Triggers
Authoring Sense. When to declare a trigger, how to name and police it, and how it composes with Execute on the same pack.
A trigger is a world change your pack is allowed to announce. It is outbound Sense, not Execute. Declare one only when the upstream system genuinely pushes.
Should this be a trigger?
| Ask | If yes |
|---|---|
| Does the upstream system push this to you (webhook, event stream, vendor notification)? | Trigger |
Do you have to ask for it with status, follow, or list? | Read command. Not a trigger |
| Is it your runtime's uptime check or a UI refresh timer? | Not a pack trigger at all |
| Did the pack's own credential go stale? | Trigger: binding.degraded |
If the source cannot push today, ship pull commands and add the stimulus when a push exists. Do not overload a command to fake it.
Declare
Two places, and both must agree.
"stimulusTriggers": [
{ "ref": "new_item" },
{ "ref": "binding.degraded" }
]"stimuli": {
"new_item": { "operation": "read", "privacy": { "land": "personal" } },
"binding.degraded": { "operation": "read", "privacy": { "land": "personal" } }
}Lint fails on a ref without a row or a row without a ref.
Naming
| Ref | Kind | Meaning |
|---|---|---|
new_item | Channel event | A new item on the linked stream |
item.updated | Channel event | An existing item changed |
binding.degraded | Binding health | Credential state moved: linked | stale | needs_reauth | pending | error |
run.completed | Domain observation | An upstream job finished |
Lower-case, dot-separated. Name the fact, not the reaction: new_item, not
please_list.
Land
Land follows the data. A personal mailbox event is personal; a shared project
stream is brain; a watched workspace directory is local. The sink records
under Principal + Binding + land, so a brain trigger still requires project
membership to be surfaced.
Isolation rules you are signing up to
- The trigger cannot mint, spend, or run a command. It can only be recorded.
- Delivery requires the runtime grant
sense.enable. Off meansdrop. - Unknown refs fail closed.
- Turning Sense off does not touch the CLI wrap.
Composition
Design the trigger and the reaction together, but keep them in their loops.
new_item ──► sink ──► Invoker: mail.get <id> ──► (maybe) mail.send --confirm
binding.degraded ──► sink ──► human: mail connect
run.completed ──► sink ──► Invoker: show <run>The reaction is Execute, under its own Policy row and grant.
Emit-only packs
If there are no user commands at all (a pure ingress source), declare
kind: "sense_only", at least one trigger, and no verbs. Do not invent a
connect unless the upstream needs a registration call.
Proof
If your pack declares Sense, its proof script includes:
one announce with sense.enable on → sink records
one announce with sense.enable off → dropped, Execute unchangedRelated
- Sense
- Sense delivery: the runtime side
- Specification: Sense