aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/design/2026-07-20-signal-pager-runbook.org193
1 files changed, 193 insertions, 0 deletions
diff --git a/docs/design/2026-07-20-signal-pager-runbook.org b/docs/design/2026-07-20-signal-pager-runbook.org
new file mode 100644
index 0000000..671b1ec
--- /dev/null
+++ b/docs/design/2026-07-20-signal-pager-runbook.org
@@ -0,0 +1,193 @@
+#+TITLE: Signal Pager Runbook
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-07-20
+
+The operational reference for the agent pager — how a page reaches Craig's
+phone, how his replies come back, how the account stays healthy, and the
+signal-cli setup behind it. This is the Signal successor to the retired ntfy
+runbook. Canonical home is rulesets because the pager is cross-machine tooling.
+
+* What the pager is
+
+One Signal identity, =+15045173983=, registered in *velox's* signal-cli
+(account file 465310, velox is the primary device). It is a dedicated pager
+number, not Craig's personal Signal. Pages go *from* that identity *to* Craig's
+own Signal account, which fires a normal mobile push on his phone.
+
+As of 2026-07-20 the identity spans two devices: velox (primary) and ratio
+(linked device "ratio-pager"). Any machine holding the account sends directly;
+a machine that doesn't relays to velox over the tailnet.
+
+Two constants the tooling depends on:
+
+- Pager account: =+15045173983= (primary on velox, linked on ratio).
+- Recipient: Craig's Signal account UUID =b1b5601e-6126-47f8-afaa-0a59f5188fde=.
+ His phone *number* reads as unregistered in Signal's directory — always
+ target the UUID, never the number.
+
+velox is the laptop that travels with Craig, so the pager account rides with
+him; ratio holds it too, so a page still lands when velox is down.
+
+* Choosing a channel
+
+"Page me" has two channels; the choice is where Craig is, and both work from any
+agent runtime (nothing here is Claude-specific). protocols.org "Paging Craig"
+is the short version pointed at every project; this runbook is the full one.
+
+- *At his laptop/desktop* — desktop notification, stays up until dismissed:
+
+ #+begin_src bash
+ notify info "Title" "Message" --persist
+ #+end_src
+
+- *Away from his machine* — the phone, over Signal:
+
+ #+begin_src bash
+ agent-page "Message for Craig's phone"
+ #+end_src
+
+When in doubt, fire both — the desktop one is free and the phone one reaches him
+if he's away.
+
+* Sending a page
+
+=agent-page= (shipped at =claude-templates/bin/agent-page=, installed to
+=~/.local/bin= by =make -C ~/code/rulesets install=) is the interface. It hides
+the machine topology by checking whether the pager account is registered in the
+local signal-cli:
+
+- If the account is local (velox's primary or a linked device like ratio), it
+ sends directly — no velox dependency.
+- Otherwise it ssh-relays the send to velox over the tailnet.
+- On failure (velox down or unreachable from a non-linked machine) it prints the
+ desktop fallback line and exits non-zero, so a caller can tell the page did not
+ land.
+
+The raw command it runs, for reference or a manual send from velox:
+
+#+begin_src bash
+signal-cli -a +15045173983 send -m "your message" b1b5601e-6126-47f8-afaa-0a59f5188fde
+#+end_src
+
+From another machine, the same send relayed over the tailnet:
+
+#+begin_src bash
+ssh velox.tailf3bb8c.ts.net \
+ "signal-cli -a +15045173983 send -m 'your message' b1b5601e-6126-47f8-afaa-0a59f5188fde"
+#+end_src
+
+Prefer =agent-page= over the raw command — it hardens the message for the remote
+shell and handles the fallback. Reach for the raw form only when debugging.
+
+* Reading replies
+
+Craig replies to a page straight from Signal on his phone. The reply is a normal
+data message *to* the pager account, so it is waiting in the pager's inbound
+queue until something receives it.
+
+Drain the queue and read what is there:
+
+#+begin_src bash
+# On velox:
+signal-cli -a +15045173983 receive --timeout 10
+# From another machine:
+ssh velox.tailf3bb8c.ts.net "signal-cli -a +15045173983 receive --timeout 10"
+#+end_src
+
+=receive= prints every queued envelope and exits 0 once the queue drains or the
+timeout elapses. A text reply from Craig arrives as an envelope from his UUID
+carrying a =Body:= line — that line is the reply text. Most envelopes are
+delivery/read receipts and typing indicators (no =Body:=); the reply you want is
+the data message with body text. For a script that waits on a reply, add
+=--send-read-receipts= so his phone shows the page was read, and parse stdout for
+the =Body:= line on an envelope from =b1b5601e-…=.
+
+Note: =receive= is destructive — it consumes the queue. Whatever drains the
+queue (an on-demand read, or the warm-keeping timer below) is what sees the
+reply, and it is seen once. An agent that pages and then waits for an answer
+should do its own =receive= rather than race the timer.
+
+* Keeping the account warm (receive timer)
+
+Signal expects a registered account to receive regularly. Left alone, the pager
+account drifts stale — signal-cli warns "Messages have been last received N days
+ago" (observed at 47 days on 2026-07-20 before a manual drain reset it). A stale
+account is a reliability risk on the one channel that reaches Craig when he is
+away.
+
+The fix mirrors roam-sync: a systemd user timer that drains the queue on a
+cadence, keeping the account warm and, as a bonus, picking up async replies. With
+the account linked on both machines, each device wants its own regular receive,
+so the timer runs on *both* velox and ratio (the shared =common= dotfiles
+package, same home as roam-sync).
+
+- Script: =scripts/signal-receive.sh= (rulesets, so both machines get it on
+ =git pull=). It no-ops cleanly on a machine that lacks the account.
+- Units: =scripts/signal-receive.service= + =.timer= (reference copies under
+ =scripts/systemd/=; the stowed copies live in =common/.config/systemd/user/=
+ of the dotfiles repo, so both machines get them).
+- Cadence: every 15 minutes (=OnUnitActiveSec=15min=), matching roam-sync.
+
+Enable on each machine (one-time, per daily-drivers.md's one-time-setup class):
+
+#+begin_src bash
+# After the dotfiles + rulesets pull, on each daily driver:
+systemctl --user daemon-reload
+systemctl --user enable --now signal-receive.timer
+systemctl --user status signal-receive.service # confirm a clean receive
+#+end_src
+
+* signal-cli setup notes
+
+- *Version:* signal-cli 0.14.5 on velox (2026-07-20).
+- *Accounts:* velox's signal-cli holds the pager identity =+15045173983= as the
+ registered primary (account file 465310). ratio's signal-cli holds two
+ accounts: Craig's personal number =+15103169357= (its own primary,
+ note-to-self only — no phone push) and the pager identity as a *linked device*
+ (Device 2, "ratio-pager", linked 2026-07-20). Both accounts coexist; target
+ the pager with =-a +15045173983=. A future daily driver joins the same way.
+- *signal-mcp:* on velox, Claude sessions may also expose a =signal-mcp= tool
+ (=send_message_to_user=, same pager identity) configured in velox's global
+ =~/.claude.json=. It works there but is invisible from any other machine and
+ from non-Claude runtimes, so =agent-page= is the portable habit. The old
+ =page-signal= shell script was removed 2026-06-12 — do not resurrect it.
+- *Linking a device:* to add a second signal-cli as a linked device of the pager
+ account (see the open decision below), provision it from the new machine and
+ approve the link from the account holder:
+
+ #+begin_src bash
+ # On the new machine — prints a tsdevice:/ URI (render as QR to approve):
+ signal-cli link -n "ratio-pager"
+ # Approve from velox (the primary device):
+ signal-cli -a +15045173983 addDevice --uri "tsdevice:/?uuid=…"
+ #+end_src
+
+* Decision — linked device (2026-07-20)
+
+The topology question — ssh-relay only vs. registering daily drivers as linked
+devices — was decided in favor of linked devices. ssh-relay only was simpler
+(one identity, one receive point) but had a single point of failure: a page
+failed when velox was down or off the tailnet.
+
+Registering ratio as a linked device removes that: ratio sends directly, so a
+page lands even when velox is down. The costs, both paid: linked-device
+provisioning per machine (the =link= / =addDevice= handshake above), and each
+device wanting its own regular =receive= — so the warm-keeping timer moved from a
+velox-only home to the shared =common= package, running on both.
+
+Adding another daily driver later is the same handshake plus a dotfiles stow;
+the timer and =agent-page= already generalize to "any machine holding the
+account."
+
+* History
+
+- 2026-07-04 — home retired ntfy (self-hosted on ratio) and tore it down,
+ switching agent paging to Signal. Handoff to rulesets to document and own.
+- 2026-07-13 — reconciled to one pager identity on velox; =agent-page= shipped
+ (direct on velox, ssh-relay elsewhere, desktop fallback); protocols.org "Paging
+ Craig" rewritten around the two channels.
+- 2026-07-20 — this runbook; receive-timer script + units added; a manual drain
+ cleared the 47-day staleness live; ratio linked as a device of the pager
+ account and its direct send verified; =agent-page= generalized to send directly
+ from any machine holding the account; receive timer moved to the shared
+ =common= package and enabled on both machines.