Skip to content
Skip to content
HCP

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?

AskIf 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.

surface.json (fragment)
"stimulusTriggers": [
  { "ref": "new_item" },
  { "ref": "binding.degraded" }
]
policy.json (fragment)
"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

RefKindMeaning
new_itemChannel eventA new item on the linked stream
item.updatedChannel eventAn existing item changed
binding.degradedBinding healthCredential state moved: linked | stale | needs_reauth | pending | error
run.completedDomain observationAn 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 means drop.
  • 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 unchanged