Trigger manifests
[[triggers]] extends harn.toml with declarative trigger registrations in the
same manifest-overlay family as [exports], [llm], and [[hooks]].
Each entry declares:
- a stable trigger
id - a trigger
kindsuch aswebhook,cron, ora2a-push - a
providerfrom the registered trigger provider catalog - a delivery
handler - optional dedupe, retry, budget, secret, and predicate settings
Shape
[[triggers]]
id = "github-new-issue"
kind = "webhook"
provider = "github"
match = { events = ["issues.opened"] }
when = "handlers::should_handle"
handler = "handlers::on_new_issue"
dedupe_key = "event.dedupe_key"
retry = { max = 7, backoff = "svix", retention_days = 7 }
priority = "normal"
budget = { daily_cost_usd = 5.00, max_concurrent = 10 }
secrets = { signing_secret = "github/webhook-secret" }
filter = "event.kind"
Handler URI schemes
Harn currently accepts three handler forms:
- local function:
handler = "on_event"orhandler = "handlers::on_event" - A2A dispatch:
handler = "a2a://reviewer.prod/triage" - worker queue dispatch:
handler = "worker://triage-queue"
Unsupported URI schemes fail fast at load time.
Local handlers and predicates resolve through the same module-export plumbing as the manifest hook loader:
- bare names resolve against
lib.harnnext to the manifest module::functionresolves either through the current manifest’s[exports]table or through package imports under.harn/packages
Validation
The manifest loader rejects invalid trigger declarations before execution:
- trigger ids must be unique across the loaded root manifest plus installed package manifests
providermust exist in the registered trigger provider cataloghandlermust be a supported URI, and local handlers must resolve to exported functionswhenmust resolve to a function with signaturefn(TriggerEvent) -> booldedupe_keyandfiltermust parse as JMESPath expressionsretry.maxmust be<= 100retry.retention_daysdefaults to7and must be>= 1budget.daily_cost_usdmust be>= 0- cron triggers must declare a parseable
schedule - cron
timezonemust be a valid IANA timezone name - secret references must use
<namespace>/<name>syntax and the namespace must match the trigger provider
Errors include the manifest path plus the [[triggers]] table index so the bad
entry is easy to locate.
Durable dedupe retention
Trigger dedupe now uses a durable inbox index backed by the shared EventLog
topic trigger.inbox. Each successful claim stores the binding id plus the
resolved dedupe_key, and duplicate deliveries are rejected until the claim’s
TTL expires.
- configure the TTL with
retry.retention_days - the default is
7days - shorter retention trims durable dedupe history sooner, which lowers storage cost but increases the chance that a late provider retry will be treated as a fresh event
Use a retention window at least as long as the provider’s maximum retry window. If a provider can redeliver for longer than your configured TTL, Harn may dispatch that late retry again once the durable claim has expired.
Doctor output
harn doctor now lists loaded triggers with:
- trigger id
- trigger kind
- provider
- handler kind (
local,a2a, orworker) - budget summary
Examples
See the example manifests under examples/triggers: