Skip to main content
Version: 1.0.0

Governed Triggers

A Touba trigger turns an event into a governed execution.

Two V1 paths are available:

Available shape
schedule -> strategy
request -> strategy

The first uses a bounded foreground runner. The second uses an explicit HTTP webhook service with a durable queue, TD-1 state, and trace. Both paths go through AIR, manifest, and runtime checks before the strategy is executed.

1. Write the Program​

src/examples/daily_research.tb
module examples.daily_research

strategy DailyResearch
takes topic as Text
do
show
with message as "Daily research run: " ++ topic

trigger DailyResearchSchedule
on schedule "every 1s"
runs strategy DailyResearch
with topic as "daily review"
limits
max_runs_per_day as 1
timeout_ms as 30000

trigger DailyResearchWebhook
on request POST "/webhook/daily-research"
runs strategy DailyResearch
with topic as payload.topic
limits
max_runs_per_day as 10
timeout_ms as 30000

The runner accepts this format:

Standard
every <n>s
every <n>m
every <n>h

Full cron support belongs to a later scheduling layer.

2. Compile​

Terminal
touba compile src/examples/daily_research.tb --out daily_research.air.json

AIR contains the trigger declaration, its target, and its limits. It contains no scheduler, secret, or external payload. payload.topic binds the event field dynamically; it is neither a dummy value nor a fallback. The manifest mapping must cover this argument; missing fields are refused before execution.

3. Activate the Trigger in the Manifest​

touba.manifest.json
{
"manifest_version": "touba-manifest-v1",
"bindings": {},
"triggers": {
"trigger:examples.daily_research/DailyResearchSchedule": {
"enabled": true,
"source": {
"kind": "schedule"
},
"limits": {
"max_runs_per_day": 1,
"timeout_ms": 30000
}
},
"trigger:examples.daily_research/DailyResearchWebhook": {
"enabled": true,
"source": {
"kind": "request"
},
"payload": {
"format": "json",
"content_types": ["application/json"],
"max_bytes": 4096,
"map": {
"topic": "$.topic"
}
},
"limits": {
"max_runs_per_day": 10,
"timeout_ms": 30000
}
}
}
}

Without this entry, the runtime refuses the trigger before executing the strategy.

4. Run the Real Trigger​

Terminal
touba trigger run daily_research.air.json \
--manifest touba.manifest.json \
--trigger trigger:examples.daily_research/DailyResearchSchedule \
--max-runs 1 \
--state touba.triggers.sqlite \
--trace-out daily_research.trace.json

Expected result after about one second:

Output
Daily research run: daily review

--max-runs sets the number of runs. The default SQLite ledger is .touba/triggers.sqlite, in the project or next to the AIR file outside a project. --state selects another .sqlite file.

The trace written to daily_research.trace.json contains the received event, trigger acceptance, execution start, and run completion.

The SQLite ledger records consumed event_ids, observed refusals, and the UTC day key used to apply max_runs_per_day, without raw payloads. If the same event is replayed, Touba refuses before relaunching the strategy with TriggerReplayRejected. An acceptance recorded on a previous day does not block the first event of the current day. The path passed to --state must end with .triggers.sqlite.

5. Run the Webhook Service​

Terminal
touba trigger serve daily_research.air.json \
--manifest touba.manifest.json \
--state touba.triggers.sqlite \
--queue touba.trigger-queue.sqlite \
--trace-dir traces \
--max-events 1

In another terminal:

Terminal
curl -X POST http://127.0.0.1:8787/webhook/daily-research \
-H 'content-type: application/json' \
-H 'x-touba-event-id: evt-webhook-001' \
-d '{"topic":"industrial adoption"}'

The service returns 202 queued, appends an entry to touba.trigger-queue.sqlite, executes the strategy, writes TD-1 state, and produces one trace per entry when --trace-dir is provided. The raw HTTP body is not injected as implicit Touba input and is not written to the queue, state, or trace; the queue keeps the payload size, the SHA-256 digest, and the arguments explicitly mapped by the manifest. Fields not listed in payload.map are ignored.

For a webhook exposed to an external provider, add --signature-secret-env TOUBA_TRIGGER_SIGNATURE_SECRET and send x-touba-signature: sha256=<hmac> computed over the exact HTTP body.

On a non-loopback host, add --auth-token-env TOUBA_TRIGGER_TOKEN and send Authorization: Bearer <token> or x-touba-trigger-token: <token>.

6. Test Checks Without Executing​

Terminal
touba run daily_research.air.json \
--manifest touba.manifest.json \
--trigger-event-json '{"trigger_ref":"trigger:examples.daily_research/DailyResearchSchedule","event_id":"evt-001","source_kind":"schedule"}' \
--trigger-dry-run

Expected result:

  • the trigger exists in AIR;
  • the manifest activates it;
  • the event source matches;
  • manifest limits do not broaden AIR limits;
  • the target strategy is not launched in dry run.

7. Execute with an Explicit Event​

Terminal
touba run daily_research.air.json \
--manifest touba.manifest.json \
--trigger-event-json '{"trigger_ref":"trigger:examples.daily_research/DailyResearchSchedule","event_id":"evt-002","source_kind":"schedule"}' \
--output trace

This mode is used for host tests or adapters. It supplies the event manually instead of waiting for the declared interval.

8. Inspect​

Terminal
touba inspect daily_research.air.json
touba inspect trace.json
touba trigger state touba.triggers.sqlite --trigger trigger:examples.daily_research/DailyResearchSchedule
touba inspect daily_research.air.json --manifest touba.manifest.json

Inspection displays what exists in the supplied artifacts. For durable trigger state (TD-1), it displays acceptance counts, refusal counts, and consumed event_ids. The webhook service exists when it is explicitly started with touba trigger serve; deployment supervision remains the responsibility of the host environment.

Retention and migration​

Trigger admission is committed before its strategy starts. A process crash therefore preserves the consumed identity. Daily counts use UTC; replay identifiers remain stored after archiving.

The ledger defaults to a 512 MiB limit. --state-max-bytes adjusts this limit (minimum 65,536 bytes). A full ledger blocks new admissions.

Read a page
touba trigger state touba.triggers.sqlite --limit 100
touba trigger state touba.triggers.sqlite --after 100 --limit 100

Use the returned next value as the next page cursor. --trigger <reference> filters events and includes that trigger’s counters.

Archive completed events
touba trigger archive-state touba.triggers.sqlite --before 2026-10-01T00:00:00Z
touba trigger archive-state touba.triggers.sqlite --before 2026-10-01T00:00:00Z \
--out archives/triggers-october.sqlite --apply

The first command previews. The second copies and verifies the archive before removing successful-event details and refusals. Active, paused and uncertain events, counters and consumed identities stay in the ledger. Read an archive with touba trigger state.

To convert an older JSON state, stop its service first:

Explicit migration
touba trigger migrate-state old.trigger-state.json --out touba.triggers.sqlite
touba trigger migrate-state old.trigger-state.json --out touba.triggers.sqlite --apply

Migration creates a new ledger and an exact .source-backup.json copy. It preserves the original file and refuses to overwrite an existing destination. Migrated admissions keep an uncertain outcome because the old ledger did not record the final result. Migration never runs an event.

Useful Refusals​

SituationExpected behavior
Trigger absent from AIRrefusal before execution
Trigger disabled in the manifestrefusal before execution
Different event sourcerefusal before execution
Manifest broadens an AIR limitrefusal before execution
Missing event_idrefusal before execution
event_id already consumed in --statereplay refusal before execution
Webhook service without --queueSQLite queue at .touba/trigger-queue.sqlite
Request payload that violates the manifest contractrefusal before enqueue
Invalid webhook signaturerefusal before enqueue
Webhook service exposed outside loopback without tokenCLI refusal before startup

Runtime Contract​

info
  • The foreground runner executes governed schedule entries.
  • The webhook service executes request -> strategy entries.
  • The durable queue follows touba-trigger-queue-v1.
  • The manifest activates each trigger explicitly.
  • Trigger state records consumed event ids and replay refusals.
  • Manifest payload contracts map explicit JSON fields to target strategy arguments.
  • HMAC signatures, bounded retries, stale processing recovery, and dead-letter states are supported by the webhook service.
  • Distributed schedulers, distributed queues, cloud adapters, manifest auto-discovery, direct agent triggering, and regulatory certification are surrounding platform layers.