Signals
Declaring signals and emitting them from rules
Signals are how a rule communicates external effects to the host application. A rule's outputs describe what is true about the input; its signals describe what should happen next.
Declaring a signal
Signal declarations live at the top level of a file, outside any rule:
signal BlockAccount(user_id)
signal NotifyUser(user_id, message)
signal Audit()A declaration names the signal and lists its positional parameters. The parameter list documents the expected arity but does not constrain types. Emitted arguments are passed through to listeners exactly as the rule produces them.
A signal can be declared once per ruleset. Re-declaring is a compile-time error.
Emitting
Inside a rule, use emit Name(args...) to queue an emission:
rule AccountApproval {
is_adult = input.age >= 18
allowed_country = input.country == "us" || input.country == "ca"
eligible = is_adult && allowed_country
emit BlockAccount(input.user_id) when !eligible
emit SendVerificationEmail(input.user_id) when eligible && !input.email_verified
}The number of arguments must match the signal declaration. The compiler checks this.
when
emit ... when <expr> makes the emission conditional. The expression
must be bool. If the condition is false or undefined, the signal
is not emitted.
emit Audit(input.user_id) when input.audit_enabledAn emit without when always fires:
emit Audit(input.user_id)How signals reach your code
After evaluation, the engine returns the list of emitted signals in
order in emitted_signals:
{
"outputs": { "approved": false },
"emitted_signals": [
{ "name": "BlockAccount", "args": ["u123"] }
]
}You can also register listeners up front:
- Go evaluator:
Evaluator.OnSignal("BlockAccount", func(args []any) error { ... })orOnSignalNamed("BlockAccount", func(args map[string]any) error { ... })which uses parameter names from the declaration. - JS evaluator:
evaluator.on("BlockAccount", (user_id) => { ... }). - CLI:
--exec "BlockAccount=./block.sh {1}"runs a shell command per emission, substituting{1},{2}, ... with the arguments.
Notes
- Signals are queued during a rule run and dispatched after all ops
complete. Emission order matches source order, filtered by
when. - Signals are local to a single evaluation. There is no persistent signal bus.
- Listeners are invoked synchronously by default. The Go evaluator
accepts a
*sync.WaitGroupto run listeners concurrently.