Skip to main content

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 referenceTrigger, 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_type and event_category equal the trigger's eventConfig values and every dataFilter row matches against data. Filter operators accept either spelling — equals | eq, not equals | neq, greater than | gt, less than | lt, includes, exists.
  • External trigger fires when event_type equals external.<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 with type: "TRIGGER", and the Batch container settles it to COMPLETED or FAILED.

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 for get/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:

StageWhere to look
Event emittedtrainingData lambda logs: [training] trigger event enqueued to …
Queue receivedtriggerEventQueue-<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

  1. Create the trigger (above) and note its id.
  2. Create an event that satisfies the filter — e.g. upload a document carrying the topic + status: published via /doc/create.
  3. Watch the dispatcher log until cronjob job queued :: {triggerId…}.
  4. Confirm sqsToBatchSubmitter submitted a Batch job whose name starts with trigger-<triggerId>-.
  5. Wait for the container; the trigger record should read status: COMPLETED, triggerCount incremented, lastEventID set.

Deployment checklist

When changes touch any of these, deploy the owning stack for the change to take effect:

ComponentSourceDeployNotes
Trigger CRUD APIbackend-core-1/public/apiserverless/trigger/* endpoints
Trigger dispatcher (matching + firing)backend-core-1/events/triggerDispatchernpm install && serverless deploybundle must include aws-sdk (Node 22 runtime does not bundle it); set cronjobExecutionQueueUrl env
Trigger poller (external events)backend-core-1/events/triggerPollerserverlessexternal triggers only
Native document eventsbackend-core-1/app/trainingDataserverlessdispatchTriggerEvent emitter; needs sqs_trigger_event_queue env
Agent eventsbackend-ai/ChatECS, backend-ai/Agent, backend-ai/Common/webhook_events.pyECS/serverlessemit_trigger_event fan-out
Trigger catalog + docsbackend-core-1/integrations/triggers/catalog.js, copies in poller + app/triggerCatalogApi, scripts/generateTriggerDocs.jsre-run generator after catalog edits
Execution containerbackend-ai/CronJobBatch (image cronjob-batch)build & push ECRreads type: "TRIGGER" jobs, writes terminal status