Trigger.dev Integration
This document explains how long-running directory generation is delegated to Trigger.dev while keeping all database writes inside the API service. It covers the runtime flow, the code that glues the two runtimes together, required environment configuration, and the release automation that deploys our Trigger.dev project.
Runtime Architecture Overview
- API request enters
DirectoryGenerationService. When a user kicks off a generation via the API (packages/agent/src/services/directory-generation.service.ts), the service callsdispatchGenerationTask(). - Optional Trigger.dev dispatch.
TriggerService(packages/agent/src/trigger/trigger.service.ts) checksconfig.trigger.shouldUseTrigger(). If the feature flag is enabled and credentials are present, it callstasks.trigger()for thedirectory-generationtask. Otherwise it falls back to the in-processprocessGeneration()path. - Trigger task fetches execution context. The task definition (
packages/agent/src/tasks/trigger/directory-generation.task.ts) boots a Nest application usingTriggerWorkerModule, fetches the directory + user context through the internal API, and runs the orchestrator. - Worker performs generation locally.
TriggerGenerationOrchestrator(packages/agent/src/trigger/trigger-generation.orchestrator.ts) drivesDataGeneratorService,MarkdownGeneratorService, andWebsiteGeneratorServiceinside the worker, updating state via the remote directory operations gateway instead of hitting the database directly. - Database writes happen through the API.
TriggerInternalApiClientforwards updates back to the API, which exposes a set of signed endpoints inTriggerInternalController(apps/api/src/trigger/trigger-internal.controller.ts). That controller uses the standardDirectoryRepositoryto persist state, keeping all DB access on the API host.
The diagram below summarizes the happy path:
Key Code Components
Trigger configuration
packages/agent/trigger.config.tsdefines the Trigger.dev project, retry profile, and build customizations (including the TypeScript decorator metadata extension required by Nest/TypeORM).- The config only scans
./src/tasks/trigger, so any new task must live under that directory to be bundled automatically.
Trigger worker runtime
-
TriggerWorkerModule(packages/agent/src/trigger/trigger-worker.module.ts) wires together:TriggerItemsGeneratorModuleandTriggerAiModule(a trimmed Nest module exposing only the services the worker needs).TriggerInternalApiClientandRemoteDirectoryOperationsService, which adapt theDirectoryOperationsinterface to HTTP calls back into the API.- The existing generator services (
DataGeneratorService,MarkdownGeneratorService,WebsiteGeneratorService).
-
directory-generation.task.tscreates a Nest application context per run, fetches the latest directory + user snapshot usingTriggerInternalApiClient.fetchDirectoryContext(), and then callsTriggerGenerationOrchestrator.run(). -
TriggerGenerationOrchestratormirrors the in-processDirectoryGenerationService.processGeneration()logic, but replaces direct repository calls withDirectoryOperationsmethods that the remote service implements. That lets us reuse the same generator services without letting the worker touch TypeORM directly.
API ↔ worker bridge
-
DirectoryOperationsinterface (packages/agent/src/directory-operations/directory-operations.interface.ts) abstracts mutating operations on thedirectoriestable. There is a database-backed implementation (DatabaseDirectoryOperationsService) used inside the API and CLI, and a remote implementation (RemoteDirectoryOperationsService) used inside the worker. -
TriggerInternalController(apps/api/src/trigger/trigger-internal.controller.ts) provides two signed routes:GET /internal/trigger/directories/:id/contextreturns a serialized directory plus its owner (including OAuth tokens for Git access).POST /internal/trigger/directories/:id/commandsaccepts typed commands derived fromDirectoryCommandPayloadsand applies them using the database-backedDirectoryOperationsimplementation.
-
Security: every request from the worker must supply the shared secret via the
x-trigger-secretheader. The controller checks it againstconfig.trigger.getInternalSecret()before serving data or accepting mutations.
Agent service integration
DirectoryGenerationService.dispatchGenerationTask()builds aDirectoryGenerationPayloadand callsTriggerService.dispatchDirectoryGeneration(). When dispatching succeeds, the method returns immediately and lets Trigger.dev complete the work asynchronously. If dispatch fails (feature disabled or Trigger.dev unavailable) it logs a warning and reverts toprocessGeneration()so users are not blocked.
Environment Variables
| Variable | Description |
|---|---|
TRIGGER_ENABLED | Feature flag checked by config.trigger.shouldUseTrigger(). Set to true to enable Trigger.dev dispatches. |
TRIGGER_SECRET_KEY | Trigger.dev secret key used by the worker to call the Trigger API. Required when TRIGGER_ENABLED=true. |
TRIGGER_API_URL | Optional override of the Trigger.dev API base URL (defaults to the hosted service). |
TRIGGER_INTERNAL_API_URL | Base URL the worker can reach for the API instance (http(s)://host[:port]/internal/trigger). No trailing slash. |
TRIGGER_INTERNAL_SECRET | Shared secret for the internal controller. Set the same value in the API environment and the worker environment. |
TRIGGER_MACHINE | Optional machine preset (e.g. standard-2x, large-1x). Leave unset to use Trigger.dev's default dev preset. |
Where to configure:
apps/api/.env.exampleandapps/cli/.env.examplelist the variables and should be used as references for local setup.- Production values belong in your deployment environment (e.g., platform secrets store). The worker needs
TRIGGER_SECRET_KEY,TRIGGER_API_URL(if customized), andTRIGGER_INTERNAL_*. The API only needs theTRIGGER_INTERNAL_*pair plusTRIGGER_ENABLEDif you want background dispatches by default.
Local Development
- Export the required environment variables (see above). For local testing you can point
TRIGGER_INTERNAL_API_URLtohttp://localhost:3100/internal/triggerand reuse a shared secret between the Nest API and the Trigger worker. - Start the API (
pnpm --filter ever-works-api devor the equivalent command you typically use). - Run the Trigger worker in watch mode via the root script:
pnpm dev:trigger. That delegates to Turbo and the@trigger.dev/sdkwatch mode inpackages/agent. - Trigger a generation through the API (e.g.,
/api/directories/:id/generate). Watch the Trigger.dev dashboard to ensure the task run appears. Tail API logs to confirm that command callbacks are updating directory state.
If you want to bypass Trigger.dev during development, either set TRIGGER_ENABLED=false or leave TRIGGER_SECRET_KEY unset. The agent service will fall back to the legacy in-process generation path.
Creating New Trigger Tasks
- Place the task definition under
packages/agent/src/tasks/trigger/so it is included bytrigger.config.ts. - Decide whether the task needs access to the database. If it does, expose the required operations through
DirectoryOperations(or another abstraction) and implement them both locally (database-backed) and remotely (webhook-backed). - Register any additional providers in
TriggerWorkerModule. Keep the worker lean—only import the modules the task actually needs. - Update API controllers or webhooks if new commands are required. Use
DirectoryCommandPayloadsto keep the request shape synchronized. - Add scripts/tests if necessary and run
pnpm --filter @packages/agent buildto verify bundling.
Deployment Pipeline
- Workflow:
.github/workflows/release-trigger-prod.ymlruns after the “CI” workflow completes onmainordevelop. It checks out the repo, installs dependencies, and executesnpx trigger.dev@latest deployinsidepackages/agent. - Secrets: The workflow expects
TRIGGER_ACCESS_TOKENto be present in the repository secrets. This token is distinct from the runtimeTRIGGER_SECRET_KEYand is only used for deployments. - Manual deploys: You can also run
pnpm deploy:triggerlocally, which delegates to the same Turbo+Trigger CLI pipeline used by the workflow.
Monitoring & Troubleshooting
- Use the Trigger.dev dashboard to inspect task runs, logs, retries, and failures.
- API logs show inbound webhook calls in
TriggerInternalController. Failures there usually mean the worker secret is misconfigured or the payload shape drifted. - If a task is dispatched but never starts, double-check the project ID in
trigger.config.tsand theTRIGGER_SECRET_KEYsupplied to the worker. - If the worker succeeds but the directory state remains unchanged, ensure the internal API base URL is reachable from the worker environment and that the controller is returning HTTP 200 for command requests.
- Review the History tab in the dashboard (Directories → Detail → History) to confirm each run produces metrics and to compare local vs. Trigger.dev executions.
Keeping this separation (Trigger.dev for long-running compute, API for persistence) lets us scale the heavy work off the API without relaxing our database access rules. When extending the system, continue funnelling all stateful operations through the internal controller and use strongly-typed payloads to guard against malformed requests.