Skip to content

How Pidge works

One channel per agent. The channel holds the bearer key (hld_…), the identity the human sees (name, color, avatar), and the human’s delivery policy for that agent. Everything an agent sends and receives is scoped to its channel — two agents never see each other’s traffic.

You pick one type (how much it may intrude — axis 1), then orthogonally decide the response (buttons? wait or not? — axis 2).

Type For Clears when
message just inform, no action needed the human opens it
important a pendency they should resolve — the recommended default Done
urgent wake them now (rare, real); can escalate to an alarm Done (cuts the alarm)
event a thing with a known time — countdown Live Activity passed / Done
live track something live (progress card, no banner) you end it

Each type maps to a delivery profile the human configured: message is a quiet banner that respects silent mode and Focus; important is time-sensitive (breaks Focus, no alarm); urgent unanswered for N minutes becomes a real AlarmKit alarm that rings through silent mode and Focus. The fuse is shown to the human up front, and answering before it fires defuses the alarm entirely — no ring, no vibration. Timing lives on the server, so the defuse works even with the app closed.

The Pidge feed on iPhone: notifications waiting for the human, each with its channel identity and age A waiting decision on the detail screen, with real action buttons the human taps to answer the agent

What makes message self-clearing is a knob called Done when read, on by default for message only and off for every other type. It lives in the app under Settings → Delivery profiles → Message; turn it off and a message stays pending until the human marks it Done, like the rest.

Composes with any type:

  • --actions yes,no / --custom-action add buttons (free-text reply is always available).
  • --wait blocks until the human answers. Without it the send is fire-and-forget — the answer arrives later via listen --all, polling, or your webhook.
  • Two shortcuts bundle both: ask = important --wait, and approval = important + Approve (Face ID) / Reject + --wait.

The human owns loudness — degrade, never reject

Section titled “The human owns loudness — degrade, never reject”

The human sets a per-channel urgency ceiling. An over-ceiling send is delivered at the allowed level instead, and the agent is told why: the API answers 201 with degraded: true and a degrade_reason (the CLI narrates it on stderr). Nothing is silently dropped, and nothing is rejected for being “too loud” — that’s the human’s policy working. Don’t retry harder.

Every response is terminal: it closes the notification. The two exceptions are snooze and a reschedule to a time, which re-fire later. A genuine follow-up question is a new notification, never a second answer on the same one. This is what keeps agent loops simple: one send, at most one answer.

Reschedule speaks natural language: the human types “20 min” or “tomorrow 9 AM” into the banner’s text field and the notification comes back exactly then — parsed in their timezone, re-fired by a durable server-side job.

Snooze re-fires, scheduled sends (--deliver-at), event countdowns and urgent-escalation are durable server-side jobs. They survive agent restarts, phone reboots and network drops — the agent never has to track a timer.

Most answers never open the app: long-press the banner and the real buttons (or a text field) are right there on the lock screen. Measured tap-to-agent latency is about one second. A reply typed on the lock screen even survives airplane mode — it’s queued on the phone and delivered automatically once the network is back, so the human can answer from a plane and the agent still hears it.

Both sides see what happened. The agent sees delivered / seen / answered on each send; the human sees WhatsApp-style ✓✓ when the agent read a message and “listening now” while an agent is blocked waiting — people type more when the light is on.

Answers and messages ride a WebSocket (wss://api.pidge.sh/cable) when the runtime has one (Node ≥ 22): sub-second delivery, and a long listen survives server deploys by reconnecting. Everything durable still goes over HTTP (backlog reads and acks), so a dropped socket costs latency, never data:

WebSocket → HTTP long-poll (?wait=, capped ~25 s) → plain GETs every ~45 s

GET https://api.pidge.sh/api/v1/manifest is the self-documenting, versioned machine spec — built from the live models so it can’t drift. With a channel key it also returns that channel’s config and a whats_new changelog. Point your agent at it.