Trigger automation — pipeline, API & traceability
Triggers let an agent run in reaction to an event instead of on a clock. This guide covers everything under the hood: the pipeline stages, the Public API for programmatic management, and how to trace a fire end-to-end when working in the dev environment.
For the product documentation (how to create a trigger in Settings), see Settings > Triggers. For the full request/response contracts, see the Public API reference — Trigger, TriggerCreateInput, TriggerUpdateInput.
The pipeline
producer queue consumer executor
─────────────────── ───────────── ────────────────────── ─────────────────────────────
Native: trainingData (create/update triggerEventQueue triggerdispatcher-<stage> cronjob-execution-queue-<stage>
/delete document), ChatECS (agent_ (SQS) → triggersByWorkspace (AppSync) → sqsToBatchSubmitter
completed/failed), Agent (planner_*) → match → fire → AWS Batch (cronjob-batch)
External: triggerPoller-<stage> (SQS ─────────────────── → job {type:"TRIGGER"} → container runs /agent SSE
→ adapter → poll)
Event envelope
All events travel through the trigger queue as a flat envelope — event_id, source and nativeEventId sit at the top level (this intentionally differs from the webhook HTTP payload, which nests them under metadata):
{
"event_type": "document.created",
"event_category": "documents",
"workspace_id": "48922607-0f1a-4993-8ff0-cf3bcfac7033",
"timestamp": "2026-08-04T07:40:11.600Z",
"data": { "document_id": 3544, "topics": ["a3da5a7e-…"], "status": "published" },
"triggered_by": "user-uuid",
"source": "api",
"event_id": "documents_1785829191251_4vzfn07ay",
"nativeEventId": "document.created"
}
Matching rules
- Native trigger fires when the envelope's
event_typeandevent_categoryequal the trigger'seventConfigvalues and everydataFilterrow matches againstdata. Filter operators accept either spelling —equals|eq,not equals|neq,greater than|gt,less than|lt,includes,exists. - External trigger fires when
event_typeequalsexternal.<provider>.<providerEvent>. - Each event is processed at most once per trigger (idempotency guard on
lastEventID). - On fire, the trigger's status flips
ARMED → FIRING, the job is submitted to the execution queue withtype: "TRIGGER", and the Batch container settles it toCOMPLETEDorFAILED.
Managing triggers via the Public API
All /trigger/* endpoints accept your API key via the x-api-key header and operate in the workspace the key belongs to (workspaceid is still required in most calls). eventConfig is a JSON string, not an object.
Create a native trigger
curl -s -X POST "https://api.toothfairylab.link/trigger/create" \
-H "Content-Type: application/json" \
-H "x-api-key: $API_KEY" \
-d '{
"workspaceid": "48922607-0f1a-4993-8ff0-cf3bcfac7033",
"name": "Summarise new docs on the Sales topic",
"agentID": "30883fa5-188f-4ac3-a361-61653e626cd8",
"eventSourceType": "NATIVE",
"eventConfig": "{\"eventType\":\"document.created\",\"eventCategory\":\"documents\",\"dataFilter\":[{\"field\":\"topics\",\"op\":\"includes\",\"value\":\"a3da5a7e-3ba3-48a3-b1a3-27527aff02bd\"},{\"field\":\"status\",\"op\":\"equals\",\"value\":\"published\"}]}",
"forcedPrompt": "Summarize this new document and list its topics.",
"isActive": true,
"status": "ARMED"
}'
The response contains the generated
id— keep it forget/update/delete.
Update / list / get / delete
# List (workspace-scoped, pageable with limit/offset)
curl -s "https://api.toothfairylab.link/trigger/list?workspaceid=$WS_ID&limit=50" -H "x-api-key: $API_KEY"
# Get full record (includes raw eventConfig, lastEventID, triggerCount, status)
curl -s "https://api.toothfairylab.link/trigger/get/$TRIGGER_ID" -H "x-api-key: $API_KEY"
# Update — only supplied fields change; pause/resume by toggling isActive / status
curl -s -X POST "https://api.toothfairylab.link/trigger/update" \
-H "Content-Type: application/json" -H "x-api-key: $API_KEY" \
-d "{\"id\":\"$TRIGGER_ID\",\"workspaceid\":\"$WS_ID\",\"agentID\":\"$AGENT_ID\",\"status\":\"ARMED\",\"isActive\":true}"
# Delete
curl -s -X DELETE "https://api.toothfairylab.link/trigger/delete/$TRIGGER_ID" -H "x-api-key: $API_KEY"
Gotcha: the bound agentID must belong to the workspace. Global agents pass trigger creation but fail at execution with Invalid agent id for this workspace. When scripting, resolve a valid agent from GET /agent/list?workspaceid=$WS_ID.
Tracing a fire end-to-end (dev)
Every stage logs to CloudWatch. To confirm a fire from event to completion:
| Stage | Where to look |
|---|---|
| Event emitted | trainingData lambda logs: [training] trigger event enqueued to … |
| Queue received | triggerEventQueue-<stage> / its DLQ (--attribute-names ApproximateNumberOfMessages) |
| Dispatcher processed | /aws/lambda/triggerdispatcher-dev-trigger-dispatcher: record …, triggersByWorkspace response, trigger not fired :: {…reason…}, cronjob job queued :: {jobId,…} |
| Job submitted to Batch | /aws/lambda/cronjobexecution-dev-sqsToBatchSubmitter: Submitted job trigger-…: Batch jobId=… |
| Agent run | /aws/batch/job log stream cronjob-def-dev/default/<hash>: [Trigger <id>] status=COMPLETED/FAILED |
| Agent SSE side | /ecs/chat-ecs (ChatECS) and /aws/ecs/tf-agent-dev-service/tf-agent-dev-container (AgentECS) |
A trigger that never leaves ARMED usually means the event never arrived (no emitter — e.g. a batch import path that bypasses /doc/create) or did not match (check trigger not fired :: reason in the dispatcher log).
E2E verification recipe
- Create the trigger (above) and note its
id. - Create an event that satisfies the filter — e.g. upload a document carrying the topic +
status: publishedvia/doc/create. - Watch the dispatcher log until
cronjob job queued :: {triggerId…}. - Confirm
sqsToBatchSubmittersubmitted a Batch job whose name starts withtrigger-<triggerId>-. - Wait for the container; the trigger record should read
status: COMPLETED,triggerCountincremented,lastEventIDset.
Deployment checklist
When changes touch any of these, deploy the owning stack for the change to take effect:
| Component | Source | Deploy | Notes |
|---|---|---|---|
| Trigger CRUD API | backend-core-1/public/api | serverless | /trigger/* endpoints |
| Trigger dispatcher (matching + firing) | backend-core-1/events/triggerDispatcher | npm install && serverless deploy | bundle must include aws-sdk (Node 22 runtime does not bundle it); set cronjobExecutionQueueUrl env |
| Trigger poller (external events) | backend-core-1/events/triggerPoller | serverless | external triggers only |
| Native document events | backend-core-1/app/trainingData | serverless | dispatchTriggerEvent emitter; needs sqs_trigger_event_queue env |
| Agent events | backend-ai/ChatECS, backend-ai/Agent, backend-ai/Common/webhook_events.py | ECS/serverless | emit_trigger_event fan-out |
| Trigger catalog + docs | backend-core-1/integrations/triggers/catalog.js, copies in poller + app/triggerCatalogApi, scripts/generateTriggerDocs.js | re-run generator after catalog edits | |
| Execution container | backend-ai/CronJobBatch (image cronjob-batch) | build & push ECR | reads type: "TRIGGER" jobs, writes terminal status |