Gates & confirmations
One contract — Gate.confirm(request) → decision — several ways to pause.
| mode | gate | what happens on ask |
|---|---|---|
| console | ConsoleGate() (default) |
terminal prompt y / n / e; a non-interactive stdin rejects |
| block | StoreGate(wait=True, notifiers=[…]) |
request persisted; Slack card / webhook sent; call blocks until decided or timeout_s |
| pending | StoreGate(wait=False) |
raises ActionPending(resume_token); fn.resume(token) later |
| interrupt | InterruptGate() |
LangGraph interrupt(); Command(resume=…) continues |
| cloud | Attest.cloud(...) |
the cloud persists + notifies; SDK polls or resumes |
| tests / CI | AutoGate("approved"), ATTEST_AUTO_APPROVE=1 |
deterministic |
from attest.gate.slack import SlackNotifier
from attest.gate.webhook import WebhookNotifier
at = Attest(gate=StoreGate(PendingStore(".attest/ledger.sqlite"), notifiers=[
SlackNotifier("xoxb-…", "#agent-approvals", inbox_url="http://localhost:8321"),
WebhookNotifier("https://your.app/attest", secret="…")]))
Where decisions come from
- Slack — Approve / Reject buttons on the card; the approver's Slack identity lands in the ledger. Wire
the interaction URL to
POST /slack/interactonattest serveor Attest Cloud (signature-verified). - Web inbox —
attest serve(local) or the dashboard (cloud): approve, reject, or approve with JSON edits. - Webhook — your UI receives the signed request and answers
POST /confirm/{id}. - Microsoft Teams —
TeamsNotifier(webhook_url, inbox_url=…, link_secret=…)posts an Adaptive Card with signed one-click Approve / Reject links (Teams webhooks cannot call back) and a link to the inbox. - Email —
EmailNotifier(to, sender=…, inbox_url=…, link_secret=…, smtp_host=…)mails the approver group (or a fallback list) the same one-click links. SetATTEST_LINK_SECRETonattest serve. - CLI —
attest confirm <id> approve --edits '{"subject": "…"}'.
Digests and anomaly flags
attest digest --since 24 [--slack-token … --slack-channel …] (cloud: GET /v1/digest) summarises actions by
agent, system, level and decision, lists every unverified row, and flags anomalies against the previous
seven days: new_action, volume_spike, unverified_spike, off_hours, external_burst.
Edits change what runs. First decision wins. Timeouts expire the request, which counts as a rejection.
Every decision records status, approver, channel, decided_at, edits.