a9script

Docs / The language & scripts

Triggers and concurrency

For an author whose script works when they press Test and now has to run by itself — and for whoever wonders why two of them never overlap.

A trigger is what starts a main script. Without one a script is code the platform holds and never runs; with one it runs every time its door delivers something. A trigger belongs to the script, not to the door, so a script’s own card is where you add it.

The five kinds

  • webhook — a system pushes to one of your endpoint paths and is acknowledged at once; your script runs afterwards.
  • api_service — a caller waits for the answer on that same kind of path. Exactly one script answers.
  • scheduler — a five-field cron in UTC, authored with the trigger.
  • email_in — a message arrived in a mailbox this environment watches.
  • system — something went wrong here: a run failed, work could not be delivered, something was marked as failing. Your script reacts to it exactly as it reacts to a webhook.

The first two name a connection and one of its existing endpoint paths. The next two name a schedule and a mailbox. system names neither — what it binds is which of the platform’s own occurrences it wants, and that is authored with the trigger. Reacting to failures is the page about the last one.

An MCP endpoint takes no trigger at all: its list is its tools. A hosted app takes none either — it answers with the files in its store and runs nothing.

when — several scripts sharing one door

A webhook or api_service trigger may carry a filter, and it is how several scripts share one endpoint instead of one script dispatching on payload fields. A filter is a set of dot-paths with expected values, all of which must match.

With no filter, a script takes every event the door delivers. With one, it takes only what it asked for. The decision is made where the event fans out, so a filtered-out event is not a run that returned early — it is no run at all, and nothing was queued.

Two consequences:

  • A filter can only ever narrow. It runs after the credential, so it can remove scripts that configuration already put on that door and can add none.
  • An event no script takes is accepted and runs nothing. The sender is answered, the miss is counted, and the log names the value that matched nobody. If a script never fires, check its filter before anything else.

One at a time, unless you say otherwise

A main script runs one run at a time. That is the default and there is no control you have to find to get it: two deliveries a second apart produce two runs, and the second waits for the first to finish.

It is the right default because that is what scripts are. They keep state across their steps, they write to datasets, and they are written expecting to be the only one doing so. Anything else would make “read this record, decide, write it back” a race the first time two events arrived together.

Say maxConcurrency on the script when the work really is independent — a number of runs that may be in flight at once. There is no ceiling in the schema; the machine’s own capacity is the real one.

Say concurrencyKey when the limit belongs to something other than the script. Several scripts naming the same key share ONE limit, which is the answer to do not hammer this account: three scripts that all write to the same system can be held to one run between them. Scripts sharing a key are expected to declare the same number.

What waiting looks like

Work that cannot start is queued, never refused. It keeps its place in line, it spends no delivery attempt, and it runs as soon as a slot frees. A burst against the default runs strictly in the order it arrived.

A script that is waiting does not hold up anything else: the queue sets it aside and the work behind it goes on. Serialization only ever holds back its own limit.

Who notices depends on who is on the other end. A webhook sender notices nothing — it was answered at the door before any of this. A caller waiting for an answer waits, and the queue time is inside its reply budget, so a long enough queue is a timeout rather than a busy answer. A schedule, a mailbox and a system occurrence have nobody to notice.

Two things deliberately take no limit of their own. A configured call reached through an endpoint is one stateless request, and the environment’s outbound rate window, the connection’s breaker and the machine’s capacity already bound it. And a Test never queues behind anything — it is how you work.

Rendered from docs/guide/triggers-and-concurrency.md in the product's own repository, at build time. Found a problem on this page? Write to the address in the footer.