# Pidge — agent operating guide

*You are an AI agent; Pidge puts rich, actionable notifications on your human's iPhone and brings
their answers back. This page is the shortest path from no key to a working loop; every field, code, cap and recipe is in the machine-readable contract at
`GET https://api.pidge.sh/api/v1/manifest` — PUBLIC, no key; add `-H "Authorization: Bearer $PIDGE_TOKEN"` for
your channel's own config. It serves a CORE, and its `sections` index gives each situational section
a size, a URL and a "read when" (`?sections=uploads,e2e` takes several). From a shell, save then read
(`curl -fsS -o /tmp/pidge-manifest.json …`): a `curl | interpreter` one-liner trips the security gates
most agent runtimes put in front of your shell, and your human sees a scary prompt.*

## Setup, once

1. **Set up** — `npx -y pidge-cli@latest setup --claim <code> --url https://api.pidge.sh`. The single-use code
   rides the prompt your human pasted; the CLI exchanges it for the key, stores it chmod 600 (scoped
   to the project directory you run setup in) and runs `doctor`. The secret never enters this chat.
   RETRY-SAFE: if setup fumbles, re-run the SAME command inside the code's 15-min TTL — you get your
   key back, nothing burns. Two agents in ONE directory need a per-agent `PIDGE_AGENT=<id>` (or their
   own `$PIDGE_TOKEN`). PIDGE_AGENT is sticky — set at setup, EVERY later command needs it.
   Rotation, revocation and the rest: manifest `auth`.
2. **Who am I** — `npx -y pidge-cli@latest doctor` (channel · device reach · claim ownership ·
   realtime). Another agent claimed this channel ⇒ STOP and tell your human. You may be INHERITING
   it: read the baton (`pidge whoami` → `agent_handoff`) and the thread
   (`GET /api/v1/messages?history=true`), then pick up where that agent stopped.
3. **First contact — the WOW** — your DEBUT on a FRESH channel. Do this FIRST, the moment setup is
   done: `npx -y pidge-cli@latest hello` (CLI >=0.11.0; on <=0.10.0 use
   `ask --template onboarding` with your own title/body). The SERVER narrates a 3-stage Live Activity
   (Connecting 1/3 → tap to confirm 2/3 → Done ✓ 3/3) so your human SEES the agent→human→agent loop
   close and trusts it — completing it IS the setup test. It BLOCKS until they answer, then prints
   the answer as JSON and exits 0. The answer can take a while, so
   keep listening (the loop below) until it lands, or you lose it. **INHERITING (step 2 found a
   baton)? SKIP the WOW** — it narrates once per channel.
4. **Learn the human** — `GET /api/v1/preferences` (tone, quiet hours, formats your fellow agents
   mapped) and `GET /api/v1/whoami` → `notify_contract` + `channel.default_profile`. It is advice, and
   your human's profiles and ceilings always win — honor all of it.
5. **Declare how you stay reachable** — `npx -y pidge-cli@latest contract set listen_mode=turn_based`
   (**Listen modes** below). Your human then sees declared-vs-observed, and the server PUSHES you a
   system message when they change what they expect (`pidge listen --all` collects it).
6. **Prove the loop** — `npx -y pidge-cli@latest selftest` drops a nonce on your queue and confirms
   your listener picks it up and acks in time, catching the orphan / `&` / transport bugs no
   checklist does.
7. **Before you exit** — leave the baton, `PATCH /api/v1/channels/:id/handoff
   {"handoff":{"current_task":"…","where_i_stopped":"…","next_steps":"…"}}`. The thread is durable;
   don't vanish silent.

## Pick the type — which IS the profile

`template_kind` and `profile` are ONE married list of 5 — you pick a name, your human owns how it
arrives (their Perfis screen): **`message`** · **`important`** (**the recommended default**, most of
what you send) · **`urgent`** (expect <1/day) · **`event`** (`--event-at <ISO8601>`) · **`live`** (a
Live Activity: `POST /api/v1/live_activities`, never ask/wait on it). Send it as `--profile <name>`;
the old names still work as aliases. Use `whoami.channel.default_profile` unless the situation calls
for another. **A decision is a separate axis**: pair `important` or `urgent` with `--actions yes,no` —
a type PLUS buttons, never a type of its own. What each does to the phone, the alias table and your
urgency ceiling: manifest `template_kinds` + `profiles`, both core so a keyless read has them.

## Stay reachable — this is the default

`keep_connection_alive` is **ON** by default: your human turned you on expecting to reach you from the
phone like a chat — and to reach YOU, the session they are working with, never another agent answering
in your place. A listener inside your session cannot deliver that by itself: the harness kills a
background task when the turn ends (observed), nobody relaunches it, and the human sees you offline.

## The watch — how you stay reachable

Run a session-length watch your harness OWNS, streaming the queue to you as events. Claude Code:
`Monitor({command:'pidge online --follow --ndjson --timeout 0', persistent:true})` — no deadline, it
lives as long as the session. One message per line (`type` message | notification_reply | system;
`batch_end` closes a batch): handle it, reply THROUGH `pidge message`, `pidge typing` when you will take
more than ~15 s, and **ONLY THEN** `pidge ack --up-to <id>`. Use the two ✓✓ with intention: being
served stamps a gray ✓✓, your ack is the GREEN ✓✓ — "I'M DONE ACTING on it"; un-acked rows re-serve
in ~10 min. No such tool in your
harness (a shell, or an agent CLI with no event stream)? Run `pidge online` in the FOREGROUND and run it
again each time it returns — you are online while you keep calling. It blocks until a message (exit 0,
the batch) or ~10 min (exit 3, nothing); with `--exec '<handler>'` the handler's exit code decides the
ack and its last `pidge-summary: <one line>` is the note. NEVER in a background terminal that does not
wake you: measured live, that is a DEAF consumer — green for the server, silent for the human. Stop
calling and you are offline: you lose TIME, never a message. If handling FAILS, do NOT ack.

When your session ends, your human sees you OFFLINE. That is correct — a contact who closed the app:
their messages wait in the queue and reach you when you reconnect. Never claim online from memory:
`pidge selftest` proves a live consumer, `pidge whoami` → `listening_state` is what they see.

**A bridge is ANOTHER agent — opt-in only.** `pidge bridge install [--handler claude|codex|gemini]
--enable` (CLI >=0.54) runs a supervised daemon that answers with a separate resumed model session while
nobody is there, proven by selftest (`bridge status` / `uninstall`). Install it only when your human
explicitly asks for a stand-in — many do not want one. A bridge YIELDS the channel to a live
`listen`/watch and takes it back when that ends, so the two never fight.

**Listen modes — declare what you actually run.** `persistent` — the watch above; `turn_based` — one
round at a time; `external_daemon` — a bridge (`bridge install` declares it). Full menu: manifest
`?sections=operating_contract`.

## Golden rules (learned the hard way)

- **Be listening when the answer lands.** Your human answers minutes-to-hours later, into your queue —
  if nothing is listening you're never woken and the answer is LOST to you. After any ask, either block
  on it (`pidge ask` blocks; or `pidge wait <correlation_id>`) or keep the `pidge listen --all` loop
  running until you have the answer.
- **Working long on one batch? Renew.** `pidge ack --ids <ids> --renew` every ~60 s heartbeats the
  ~10-min lease AND your presence (`pidge bridge` >=0.26 does it for you) — a working agent should
  never read as offline.
- **One listener only** — two = double-ack and churn. Check with `ps -eo pid,args | grep
  "[p]idge.*listen"`. **Never `pkill -f "…listen"`**: it can kill your own shell.
- **Single process.** `npx` spawns a tree (npm→sh→node); when your harness stops the task the leaf can
  ORPHAN and keep consuming the channel without waking you. The long-poll (`--no-realtime`) is the
  robust floor.
- **Never trust your own sense of time between turns** — a turn-based agent compresses idle gaps and
  misreads them, even as a "clock bug". Cross-check the server `Date` header before concluding anything
  temporal.
- **Transport is fragile.** A socket can drop (code 1006), and your host sleeping or waking looks like a
  dead round from inside. The CLI degrades to the long-poll itself, and its exit narration separates
  "your network is down (reconnect, relaunch)" from "the channel is broken (tell your human)" — trust it
  instead of escalating on the first blip.
- **Don't burn the human's attention** — use `reply` (not `decision`) to pull free text; a `decision`'s
  "Yes" button steals the click. Never send a `decision` for an FYI.
- **Closing a Live Activity that COMPLETED?** Send `progress: 1.0` on the `end` — an `end` can be a
  cancel at 50%, so the app leaves progress neutral unless you say "done".
- **Turn-based does NOT mean unreachable.** Running the loop keeps you continuously reachable — real
  agents show days of it.
