Listeners — how an automation is triggered

Listeners — how an automation is triggered #

Use listen to say when an automation runs. Everything that starts a run — a live event, a schedule's tick, a person clicking "Run now" — arrives through a listen line.

listen #

inbox = connect `@lf/email`
…
listen as "Deal intake" to inbox-[e:Emails WHERE e.Key == "deals"]-> fire `Intake`

Saving the file registers its listeners; if one can't go live, the save says so.

where-a-filter-is-judged #

A listen may filter any hop: chat-[ch:Channels WHERE ch.Name == "deals"]->-[m:Messages WHERE m.Message CONTAINS "intro"]->. A WHERE reads only its own hops and literals — never a name a run holds, ai(…), or another walk. Each part of it is judged in one of three places:

A filter on a hop that arrivals do not carry and that is not cheap to list is refused when you save, with the way out: filter on what arrives, or wait on one record in a run.

listen-fire listens shows each listen: how each hop's filter is honoured (arrival, resolved, unfiltered), what it watches, and whether it is ok or paused.

edges-that-need-a-filter #

Some edges span far more than one listen should hear: every schedule (Ticks), every team's mail (Emails). A listen on one names the values it wants on the field the system narrows it by, with == or IN [ … ] and literals: t.Schedule == "0 9 * * 1", e.Key == "deals". Without that the save is refused, showing the filter to write.

shared-polls #

Listens on one connection and edge with the same filter share one poll or one webhook. A polled system (Granola, Gmail) is polled at the interval its edge declares; a deployment sets another per connection (pollEvery in lf.yaml), and a listen sets none.

loops #

A run's writes can fire more runs: a two-way sync hears its own write come back, and two automations can feed each other. The host breaks every loop:

Filter a sync's listen to the fields it copies (c.Field == "Name"), so its other writes never fire it.

an-arrival-not-judged #

A listen that cannot tell whether an arrival fires it — its filter reads a field the system could not give — runs nothing for it, while the arrival's other listens run as usual. The host reports it on the system's Events as Arrival Not Judged, with the listen and the delivery:

listen to sys-[e:Events WHERE e.Kind == "Arrival Not Judged"]-> fire `Report Unjudged`

Once the system answers again, listen-fire listens replay <file>:<movement> --delivery <id> puts that delivery through that listen alone. A listen that ran the arrival before runs nothing; --force runs it again.

waiting-in-a-fired-run #

inbound-mail #

connect `@lf/email` is inbound mail. Which carrier delivers it is a setting of the deployment, not a choice an automation makes.