Notification & Consent System¶
Multi-surface notifications, requests, and consent — Obsidian, Telegram, Dashboard
Details¶
The notification system (work_buddy/notifications/) enables real-time human-in-the-loop interaction across three surfaces: Obsidian modals, Telegram messages, and the web dashboard. This is the primary mechanism for agents to communicate with the user, collect decisions, and request consent — without the user needing to be in the same terminal session.
When to use¶
- Notify the user of events (journal updated, task synced, build complete) — fire-and-forget (
response_type: "none") - Request a decision (yes/no, pick from choices, freeform text input) — blocks or polls for response
- Request consent for protected operations — specialized request with grant/deny/temporary options
- Reach the user on their phone via Telegram when they're away from the computer
Model¶
- Notification — a message that may not need a response (
response_type: "none") - Request — expects a response:
boolean,choice,freeform,range, orcustom - Consent Request — specialized choice request with
always/temporary/once/denyoptions
Each notification gets a unique ID (req_XXXXXXXX). Requests also get a 4-digit short ID (e.g., #4920) for easy reference on Telegram via /reply 4920 yes.
Surfaces and first-response-wins¶
All notifications are delivered to all available surfaces simultaneously. When the user responds on any one surface, the others are automatically dismissed (Obsidian modal closes, Telegram message updates to "Responded on [surface]", dashboard view removed).
See notifications/surfaces for surface-specific strengths, limitations, and response-type rendering.
Using the system¶
Send a notification (fire-and-forget):
`mcp__work-buddy__wb_run("notification_send", {
"title": "Build complete",
"body": "All tests passed."
})`
Request a decision (blocking poll):
`mcp__work-buddy__wb_run("request_send", {
"title": "Archive completed tasks?",
"body": "10 done tasks found. Move to archive?",
"response_type": "boolean",
"timeout_seconds": 90
})`
Consent for wb_run operations is handled by the gateway automatically — when a @requires_consent gate fires inside a capability you invoke, the gateway delivers the notification, polls for the user's response, and writes the grant on approval. You receive {status: "granted"}, {status: "denied"}, or {status: "timeout"} from your original wb_run call. No manual orchestration. See <
TTL and expiry¶
Notifications expire after 1 hour, requests after 2 hours. Expired notifications are swept lazily on list_pending(). The expires_at field is set automatically in create_notification().
Callback dispatch on response¶
callback_session_idset → dispatched via messaging service for AgentIngest hook deliverycallbackset → dispatched as messaging payload for sidecar executor- Neither → just update the record; requester polls on next check
MCP capabilities¶
| Capability | Purpose |
|---|---|
notification_send |
Fire-and-forget notification. Optional surfaces param |
request_send |
Create + deliver a request. Optional timeout_seconds for blocking poll, surfaces for targeting |
request_poll |
Check/wait for response to a previously delivered request |
consent_request |
One-call consent flow: create + deliver + poll + auto-resolve |
consent_request_resolve |
Manual approve/deny for deferred consent (after timeout or late response) |
consent_request_list |
List pending consent requests |
consent_grant |
Direct grant manipulation (low-level; see notifications/consent) |
consent_revoke |
Revoke a consent grant |
consent_list |
List all grants with status |
notification_list_pending |
List all pending notifications/requests |
Consent flow (gateway-managed)¶
For wb_run operations, consent is handled transparently by the gateway — see notifications/consent for the full picture. The flow below applies to manual consent_request calls for non-gateway operations:
consent_request({operation, reason, risk, timeout_seconds: 90})— one call.- Obsidian modal + Telegram message + Dashboard toast appear with consent choices.
- User responds on any surface → grant auto-written, other surfaces dismissed, result returned.
- If timeout →
{status: "timeout", request_id: "..."}returned; request stays pending. - Agent can
request_polllater, thenconsent_request_resolve. - Or user responds after timeout → callback dispatched via messaging.