Policy and quiet hours
A policy is the behaviour record for one conversation. [policy] in
everasync.toml is the default; a policy stored for a specific channel +
conversation overrides it, and the /async slash command writes those
overrides from inside the conversation itself.
If the lookup fails, the default is used and a warning is logged — a storage hiccup never changes behaviour silently.
The fields
[policy]
enabled = true
sensitivity = "conservative"
nudge_delay_secs = 150
min_confidence = 0.7
unfurl_links = true
# [policy.quiet_hours]
# start = "22:00"
# end = "07:00"
enabled — master switch (default true)
false stops the pipeline for that conversation immediately after the policy
lookup: no classification, no connector calls, no nudge. Events are still
recorded.
sensitivity — "conservative" (default) · "balanced" · "high"
How much of the ambiguous middle is acted on, and whether an AI provider is consulted at all. Full treatment in sensitivity.
nudge_delay_secs — grace window (default 150)
How long the author has to fix the message themselves before the private nudge
fires. A thread reply or an edit from the author cancels it. Accepted range
through /async delay is 0–3600 seconds.
Shorter is not better. The window is what converts "you were nudged" into "you fixed it before anything happened", and the cancel counter is a feature, not waste — see the private-nudge rule.
min_confidence — public-posting bar (default 0.7)
The minimum connector confidence before a context card is posted publicly. This is a completely different number from the sensitivity thresholds: those gate the AI's opinion about the message, this gates a connector's certainty that it found the right artifact.
Roughly what connectors return:
| Situation | Typical confidence |
|---|---|
Reference carried an exact key (EVER-123, PR #482) | 0.95 |
| Author has exactly one open candidate | 0.80–0.85 |
| Several candidates, best match by recency + keyword overlap | ≤ 0.75 |
| Ever Gauzy, any match | capped at 0.80 while the user map is hand-maintained |
Raise it toward 0.9 if the wrong artifact ever gets posted; a bad public card
is worse than no card. Lower it toward 0.6 only in a channel where everyone
works on one repo.
unfurl_links — enrich links the author already posted (default true)
With it on, URLs already in the message are handed to the connectors alongside the prose references. The platform's own preview gives you a title; a connector gives you "open · review requested · 2 approvals". Connectors claim the hosts they own and ignore the rest.
Turn it off in a channel where people paste a lot of links and do not want a reply on each:
/async unfurl off
quiet_hours — optional UTC window
[policy.quiet_hours]
start = "22:00"
end = "07:00"
Inside the window, no nudge is scheduled. start > end wraps midnight, so
the example above is the expected 22:00→07:00 night.
Two things worth knowing:
The window is evaluated against UTC, not the workspace's timezone — a distributed team should pick the window that covers the hours nobody should be pinged in, not one team's local night.
Quiet hours do not suppress context cards. A card is useful whenever somebody reads the thread; a nudge at 3am is a notification nobody asked for.
The /async command
The slash command edits the current conversation's policy and always replies privately — Slack renders slash-command responses ephemerally.
| Command | Effect |
|---|---|
/async or /async status | Report enabled, sensitivity, nudge delay, min confidence, link enrichment, and the AI provider + model in use |
/async on | Enable for this conversation |
/async off | Disable for this conversation |
/async sensitivity high|balanced|conservative | Set sensitivity |
/async delay <secs> | Set the grace window (0–3600) |
/async unfurl on|off | Toggle link enrichment |
/async charter | Print the charter in force |
/async digest | Counters for the last 7 days |
Anything unrecognised replies with the command list. /async status is the
fastest way to confirm a whole install is live — it exercises HTTPS ingress,
signature verification and the bot token in one round trip.
Example status reply:
Ever Async here is *ON* — sensitivity `Conservative`, nudge delay 150s,
min confidence 70%, link enrichment on.
AI: OpenRouter (+1 fallback) · `anthropic/claude-sonnet-5`
The REST equivalent
The dashboard and any script use the same store:
# read — JSON null when nothing is stored (the client falls back to defaults)
curl -s http://localhost:8100/api/v1/policies/slack/C0123ABC
# write — a FULL policy object; 204 No Content on success
curl -X PUT http://localhost:8100/api/v1/policies/slack/C0123ABC \
-H 'content-type: application/json' \
-d '{
"enabled": true,
"sensitivity": "balanced",
"nudge_delay_secs": 120,
"min_confidence": 0.75,
"quiet_hours": null,
"unfurl_links": true
}'
PUT replaces the whole record — there is no partial update. Read, modify,
write back.
/api/v1 is unauthenticated by default[auth] mode defaults to local_trusted — no credential is required and every
caller is the local board principal — and /api/v1 carries permissive CORS.
That is the right default for a private network and the wrong one for a public
hostname: the two calls above are exactly how a stranger silences the bot in your
channels.
Turn on the optional [auth] block before
you publish it. Under mode = "authenticated" the read above needs viewer and
the write needs operator (action policy.write). Ingress is different: it
is protected by each platform's request signature, which is why it stays
anonymous. → Security & authentication