Producer & Application Integration Guide¶
This guide provides step-by-step patterns for application developers integrating with UNS as a notification producer and acknowledgment consumer, using either the embedded Python runtime (uns.Runtime + uns.Client) or the remote HTTP REST API (/api/v1/notifications).
1. Choosing Your Producer Integration Mode¶
| Criterion | Embedded Python (uns.Runtime + uns.Client) |
Remote HTTP Service (POST /api/v1/notifications) |
|---|---|---|
| Language Support | Python >= 3.11 |
Any language (curl, httpx, fetch, Go, Rust) |
| Authentication | Host-supplied principal: str on uns.Client(runtime, principal=...) |
Authorization: Bearer <UNS_API_KEY> (or X-UNS-API-Key) |
| Database Access | Direct SQLAlchemy access to UNS_DATABASE_URL |
No direct DB access required; uns server owns DB |
| Worker Lifecycle | Application runs await runtime.worker() or await runtime.run_once() |
uns HTTP server runs runtime.worker() automatically |
| Terminal Callbacks | Native Python async def callback(event: dict) passed to Runtime |
Poll GET /api/v1/notifications/{id} or /ack-status |
2. Cookbook 1: Embedded Python Producer (uns.Runtime + uns.Client)¶
Step 2.1: Initialize Runtime and Client¶
import asyncio
from typing import Any
from uuid import uuid4
from uns.responses import AutonomyContextRequest, SendNotificationRequest
from notification.adapters import EmailAdapter, EmailConfig, SlackAdapter, SlackConfig
from notification.models import AutonomyMode, NotificationIntent
from notification.service import SimpleAdapterRegistry
from uns import Client, Runtime
from uns.runtime import Conflict
async def on_terminal_response(event: dict[str, Any]) -> None:
"""At-least-once callback invoked when a notification is ACKED, DISMISSED, or TIMED_OUT.
Always deduplicate by event["id"] in downstream consumers.
"""
print(
f"[Callback] event_id={event['id']} notification_id={event['notification_id']} "
f"kind={event['kind']} payload={event['payload']}"
)
async def run_producer_example() -> None:
# 1. Register channel adapters
adapters = SimpleAdapterRegistry()
adapters.register(
SlackAdapter(SlackConfig(bot_token="xoxb-your-token", default_channel="#ops-alerts"))
)
# 2. Initialize durable Runtime against SQLite
runtime = Runtime(
"sqlite:///./uns.db",
adapters=adapters,
create_tables=True,
callback=on_terminal_response,
)
# 3. Create a principal-scoped Client
client = Client(runtime, principal="order-execution-service")
# 4. Build a notification request
agent_id = uuid4()
request = SendNotificationRequest(
notification_type="trading.circuit_breaker",
intent=NotificationIntent.SAFETY,
title="Circuit Breaker Tripped",
message="Order velocity exceeded 500 orders/min; execution paused.",
source="order_router",
idempotency_key="cb-trip-2026-10-22-window-01",
dedup_key="circuit_breaker:order_router:velocity",
correlation_id="trace-98123",
autonomy_context=AutonomyContextRequest(
autonomy_mode=AutonomyMode.LIVE,
agent_id=agent_id,
),
metadata={
"orders_per_min": 542,
"limit": 500,
},
)
# 5. Optional: Dry-run policy simulation (no DB writes, no network effects)
sim = await client.simulate(request)
print("Simulated channels:", sim.resolved_channels, "mode:", sim.resolved_delivery_mode)
# 6. Send notification (accepts into outbox + attempts immediate dispatch)
response = await client.send(request)
print("Status:", response.status, "ID:", response.notification_id)
# 7. Acknowledge the notification (if require_ack=True and status==DELIVERED)
if response.require_ack and response.status.value == "DELIVERED":
ack = runtime.acknowledge(
response.notification_id,
actor="oncall-engineer",
metadata={"action": "verified_halt"},
)
print("Ack status:", ack.ack_status, "at:", ack.acked_at)
await runtime.deliver_callbacks()
runtime.engine.dispose()
3. Cookbook 2: Remote HTTP Producer (POST /api/v1/notifications)¶
Step 3.1: Sending a Signal with Idempotency and Inspecting status¶
When calling POST /api/v1/notifications, the server returns HTTP 202 Accepted as soon as the obligation is committed to the outbox and the initial dispatch attempt finishes. Always check the JSON status field in the response body:
import httpx
UNS_URL = "http://127.0.0.1:8000"
HEADERS = {"Authorization": "Bearer replace-with-your-local-test-key"}
payload = {
"notification_type": "system.alert",
"intent": "SAFETY",
"title": "Database Replica Lag High",
"message": "Read replica lag is 42s (threshold 10s).",
"source": "db_health_monitor",
"idempotency_key": "replica-lag-incident-20261022-01",
"dedup_key": "db_replica_lag:primary_cluster",
"autonomy_context": {
"autonomy_mode": "LIVE",
"agent_id": "11111111-2222-3333-4444-555555555555",
},
"metadata": {
"lag_seconds": 42,
"cluster": "prod-us-east-1",
},
}
with httpx.Client(base_url=UNS_URL, headers=HEADERS, timeout=15.0) as client:
resp = client.post("/api/v1/notifications", json=payload)
if resp.status_code == 409:
raise RuntimeError("Idempotency key reused with different payload!")
if resp.status_code == 422:
raise ValueError(f"Policy or schema validation error: {resp.json()}")
resp.raise_for_status()
result = resp.json()
notif_id = result["notification_id"]
print(f"Accepted notification {notif_id}: status={result['status']}")
Step 3.2: Understanding idempotency_key vs. dedup_key¶
| Field | Scope | Window | Behavior on Match |
|---|---|---|---|
idempotency_key |
(principal, source, idempotency_key) |
Until expired by uns-maintain (idempotency_seconds) |
Exact request retry protection: - Identical payload $\rightarrow$ returns original notification_id with idempotent_replay: true.- Different payload $\rightarrow$ raises 409 Conflict. |
dedup_key |
(principal, source, dedup_key, effective_hash) |
Policy's dedup_window_seconds (default 300s) |
Alert storm coalescing: - Creates a new notification_id row linked via dedup_owner_id to the active window leader.- If the leader is already DELIVERED, the new row immediately settles as DEDUPLICATED.- If the leader is still PENDING, the new row waits as a follower (PENDING) and is either satisfied (DEDUPLICATED) when the leader delivers or promoted (DEDUP_OWNER_PROMOTED) if the leader exhausts all retries. |
4. Producer Rules for Policies & Metadata¶
- Never pass adapter-routing secrets or recipient overrides in
metadata: Keysslack_channel,email_to,pagerduty_severity, andwebsocket_user_idare reserved and rejected with422 PolicyOverrideError. - Respect
ACTIVEpolicy authority: If anACTIVEpolicy exists fornotification_type, omitdelivery_mode,require_ack, andpriorityfrom your producer payload (or ensure they match the policy's resolved values), otherwisePolicyRuntimerejects the request with422. - Notification is not action approval:
Receiving
status: "DELIVERED"orack_status: "ACKED"records that a human or operator acknowledged the notification obligation; it is not an automated execution-approval or capability-grant mechanism (docs/PRODUCT.md).