diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-13 18:30:48 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-13 18:30:48 -0500 |
| commit | 49be354ff18d6739e478ee4f5ab46de6cb9e4f81 (patch) | |
| tree | 80bffd6f32c39c2f98a0cd8dea86aba766b8d43b | |
| parent | 0de6e09a4a6014270b0a8b77cfac7661b8cdf5dd (diff) | |
| download | rulesets-49be354ff18d6739e478ee4f5ab46de6cb9e4f81.tar.gz rulesets-49be354ff18d6739e478ee4f5ab46de6cb9e4f81.zip | |
feat(pager): add agent-page, the runtime-neutral phone pager
The Signal pager reconcile found one pager identity, registered in velox's signal-cli, reachable from other machines only by hand-rolled ssh. agent-page wraps that: direct signal-cli on velox, ssh relay over the tailnet from everywhere else, a desktop-fallback hint when the relay fails, and the UUID target baked in so nobody pages the unregistered phone number again. Four bats tests stub ssh/uname/signal-cli to verify command construction; the real path was verified with a live phone push today. protocols.org's Paging Craig section now teaches both channels (notify for the desk, agent-page for the phone) and demotes signal-mcp to a velox-local nicety; page-me.org, work-the-backlog, and the INDEX carry the same two-channel story. Every project inherits on its next startup sync and make install.
| -rw-r--r-- | .ai/protocols.org | 21 | ||||
| -rw-r--r-- | .ai/workflows/INDEX.org | 4 | ||||
| -rw-r--r-- | .ai/workflows/page-me.org | 13 | ||||
| -rw-r--r-- | .ai/workflows/work-the-backlog.org | 2 | ||||
| -rw-r--r-- | claude-templates/.ai/protocols.org | 21 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/INDEX.org | 4 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/page-me.org | 13 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/work-the-backlog.org | 2 | ||||
| -rwxr-xr-x | claude-templates/bin/agent-page | 48 | ||||
| -rw-r--r-- | scripts/tests/agent-page.bats | 68 | ||||
| -rw-r--r-- | todo.org | 6 |
11 files changed, 188 insertions, 14 deletions
diff --git a/.ai/protocols.org b/.ai/protocols.org index ebe14d1..5cd69d4 100644 --- a/.ai/protocols.org +++ b/.ai/protocols.org @@ -412,14 +412,27 @@ Full usage: =notify --help= or see =~/.local/bin/notify= - =atq= - list all scheduled alarms - =atrm [number]= - remove an alarm by its queue number -** Paging Craig — desktop vs. away from the machine +** Paging Craig — the agent pager -"Page me" has two channels; pick by where Craig is. +"Page me" has two channels; pick by where Craig is. Both work from any agent runtime — nothing here is Claude-specific. - *At his laptop/desktop* — desktop =notify ... --persist= (above). It reaches him on the machine and stays up until dismissed. -- *Away from his laptop/desktop* — page his phone over Signal via the *signal-mcp* tool =send_message_to_user=, addressed to Craig's account UUID =b1b5601e-6126-47f8-afaa-0a59f5188fde= (his primary number reads as unregistered in Signal's directory — never page a phone number). The message goes out from the dedicated pager account (+15045173983) and fires a normal mobile push. This is the live cross-device path, verified working 2026-06-30. -Do *not* use the old =page-signal= shell script — it was removed from the rulesets canonical 2026-06-12 and its =~/.local/bin/page-signal= symlink no longer exists. The signal-mcp tool is the only supported Signal path; =notify --persist= is the only supported desktop path. + #+begin_src bash + notify info "Title" "Message" --persist + #+end_src + +- *Away from his laptop/desktop* — page his phone over Signal with the *agent pager*: + + #+begin_src bash + agent-page "Message for Craig's phone" + #+end_src + + =agent-page= (in =~/.local/bin= via the rulesets install) sends from the dedicated pager identity (+15045173983, registered in velox's signal-cli) to Craig's Signal account UUID, firing a normal mobile push. On velox it sends directly; on any other tailnet machine it ssh-relays the send to velox. Verified end to end 2026-07-13. Never page Craig's phone *number* — it reads as unregistered in Signal's directory; the script already targets the UUID. + + Caveats: velox must be up and on the tailnet (the script says so and names the desktop fallback when the relay fails), and the signal-cli account wants a periodic =receive= — both tracked on the rulesets Signal-pager task, which owns the full runbook. + +On velox, Claude sessions may also have the *signal-mcp* tool (=send_message_to_user=, same pager identity) — fine to use there, but it exists only in velox's local MCP config, so =agent-page= is the portable habit. Do *not* use the old =page-signal= shell script (removed 2026-06-12). * Session Protocols diff --git a/.ai/workflows/INDEX.org b/.ai/workflows/INDEX.org index c62c296..b031dbe 100644 --- a/.ai/workflows/INDEX.org +++ b/.ai/workflows/INDEX.org @@ -110,8 +110,8 @@ This index must list every =.org= file in =.ai/workflows/= except this one and e - Situational triggers: "broadcast the <event> to all projects", "broadcast that <situation>", "let every project know I'll be away ..." - =flashcard-review.org= — review an org-drill flashcard file, restructure cards to question-form headings (no answer hints), audit content accuracy against project source-of-truth via subagent, rewrite source preserving SRS state, regenerate the Anki =.apkg= to =~/sync/phone/anki/=. Person cards use "Who is X? Tell me about their Y."; talking-points cards stay as-is. Script behavior: =flashcard-to-anki.py= strips =:PROPERTIES:= drawers + =SCHEDULED:= / =DEADLINE:= planning lines from Anki output. - Triggers: "review the flashcards", "update the flashcards", "review the drill deck", "update the drill deck", "refresh the Anki cards", "let's run the flashcard-review workflow" -- =page-me.org= — set a timed notification. - - Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm") +- =page-me.org= — set a timed notification (desktop =notify=; phone via =agent-page= when Craig is away). + - Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm", "page my phone") - =status-check.org= — proactive long-running-job updates. - Triggers: "keep me posted on this", "provide status checks on this job", "let me know when it's done", "monitor this for me". Auto: any job estimated 10+ min. - =create-workflow.org= — define a new workflow. diff --git a/.ai/workflows/page-me.org b/.ai/workflows/page-me.org index dad5da6..bfa92c6 100644 --- a/.ai/workflows/page-me.org +++ b/.ai/workflows/page-me.org @@ -113,6 +113,19 @@ notify info "Page" "Your message here" --persist The =--persist= flag keeps the notification on screen until manually dismissed. All page-me notifications should use =--persist= by default. +** Paging Craig's phone (away from the machine) + +The timed =notify= alarm above is the desktop channel. When Craig is away from the machine (or asks to be paged "on my phone"), use the agent pager instead — a Signal push to his phone from any machine or agent runtime: + +#+begin_src bash +agent-page "Build finished — ready for your eyes" + +# Timed phone page: same at-daemon pattern, different channel +echo "agent-page 'Meeting starts in 5'" | at 3:25pm +#+end_src + +Channel selection and the pager's mechanics live in protocols.org "Paging Craig — the agent pager". When in doubt, fire both: the desktop notification persists for whenever he returns, the phone push reaches him now. + ** Managing Alarms #+begin_src bash diff --git a/.ai/workflows/work-the-backlog.org b/.ai/workflows/work-the-backlog.org index 462e6b2..090841d 100644 --- a/.ai/workflows/work-the-backlog.org +++ b/.ai/workflows/work-the-backlog.org @@ -152,7 +152,7 @@ With paging on, fire one page when the set is done or the cap is hit — end-of- notify info "Page" "<project>: <N> done, <M> remaining — <one-line summary>" --persist #+end_src -=--persist= keeps it on screen until dismissed, and =info= is the page-me urgency convention (persistent but never crash-scary). The page fires when the set completes *or* the cap stops the run — either way exactly once. The message carries the project name, the completed count, and the remaining count (with skipped tasks noted in the run summary) so Craig can confirm ready and name the next project in one reply. There is no separate page-signal call — =notify= is the paging surface. +=--persist= keeps it on screen until dismissed, and =info= is the page-me urgency convention (persistent but never crash-scary). The page fires when the set completes *or* the cap stops the run — either way exactly once. The message carries the project name, the completed count, and the remaining count (with skipped tasks noted in the run summary) so Craig can confirm ready and name the next project in one reply. =notify= is the desktop paging surface; a run that expects Craig to be away also fires =agent-page= with the same message (the Signal phone channel — protocols.org "Paging Craig — the agent pager"). * Metrics diff --git a/claude-templates/.ai/protocols.org b/claude-templates/.ai/protocols.org index ebe14d1..5cd69d4 100644 --- a/claude-templates/.ai/protocols.org +++ b/claude-templates/.ai/protocols.org @@ -412,14 +412,27 @@ Full usage: =notify --help= or see =~/.local/bin/notify= - =atq= - list all scheduled alarms - =atrm [number]= - remove an alarm by its queue number -** Paging Craig — desktop vs. away from the machine +** Paging Craig — the agent pager -"Page me" has two channels; pick by where Craig is. +"Page me" has two channels; pick by where Craig is. Both work from any agent runtime — nothing here is Claude-specific. - *At his laptop/desktop* — desktop =notify ... --persist= (above). It reaches him on the machine and stays up until dismissed. -- *Away from his laptop/desktop* — page his phone over Signal via the *signal-mcp* tool =send_message_to_user=, addressed to Craig's account UUID =b1b5601e-6126-47f8-afaa-0a59f5188fde= (his primary number reads as unregistered in Signal's directory — never page a phone number). The message goes out from the dedicated pager account (+15045173983) and fires a normal mobile push. This is the live cross-device path, verified working 2026-06-30. -Do *not* use the old =page-signal= shell script — it was removed from the rulesets canonical 2026-06-12 and its =~/.local/bin/page-signal= symlink no longer exists. The signal-mcp tool is the only supported Signal path; =notify --persist= is the only supported desktop path. + #+begin_src bash + notify info "Title" "Message" --persist + #+end_src + +- *Away from his laptop/desktop* — page his phone over Signal with the *agent pager*: + + #+begin_src bash + agent-page "Message for Craig's phone" + #+end_src + + =agent-page= (in =~/.local/bin= via the rulesets install) sends from the dedicated pager identity (+15045173983, registered in velox's signal-cli) to Craig's Signal account UUID, firing a normal mobile push. On velox it sends directly; on any other tailnet machine it ssh-relays the send to velox. Verified end to end 2026-07-13. Never page Craig's phone *number* — it reads as unregistered in Signal's directory; the script already targets the UUID. + + Caveats: velox must be up and on the tailnet (the script says so and names the desktop fallback when the relay fails), and the signal-cli account wants a periodic =receive= — both tracked on the rulesets Signal-pager task, which owns the full runbook. + +On velox, Claude sessions may also have the *signal-mcp* tool (=send_message_to_user=, same pager identity) — fine to use there, but it exists only in velox's local MCP config, so =agent-page= is the portable habit. Do *not* use the old =page-signal= shell script (removed 2026-06-12). * Session Protocols diff --git a/claude-templates/.ai/workflows/INDEX.org b/claude-templates/.ai/workflows/INDEX.org index c62c296..b031dbe 100644 --- a/claude-templates/.ai/workflows/INDEX.org +++ b/claude-templates/.ai/workflows/INDEX.org @@ -110,8 +110,8 @@ This index must list every =.org= file in =.ai/workflows/= except this one and e - Situational triggers: "broadcast the <event> to all projects", "broadcast that <situation>", "let every project know I'll be away ..." - =flashcard-review.org= — review an org-drill flashcard file, restructure cards to question-form headings (no answer hints), audit content accuracy against project source-of-truth via subagent, rewrite source preserving SRS state, regenerate the Anki =.apkg= to =~/sync/phone/anki/=. Person cards use "Who is X? Tell me about their Y."; talking-points cards stay as-is. Script behavior: =flashcard-to-anki.py= strips =:PROPERTIES:= drawers + =SCHEDULED:= / =DEADLINE:= planning lines from Anki output. - Triggers: "review the flashcards", "update the flashcards", "review the drill deck", "update the drill deck", "refresh the Anki cards", "let's run the flashcard-review workflow" -- =page-me.org= — set a timed notification. - - Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm") +- =page-me.org= — set a timed notification (desktop =notify=; phone via =agent-page= when Craig is away). + - Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm", "page my phone") - =status-check.org= — proactive long-running-job updates. - Triggers: "keep me posted on this", "provide status checks on this job", "let me know when it's done", "monitor this for me". Auto: any job estimated 10+ min. - =create-workflow.org= — define a new workflow. diff --git a/claude-templates/.ai/workflows/page-me.org b/claude-templates/.ai/workflows/page-me.org index dad5da6..bfa92c6 100644 --- a/claude-templates/.ai/workflows/page-me.org +++ b/claude-templates/.ai/workflows/page-me.org @@ -113,6 +113,19 @@ notify info "Page" "Your message here" --persist The =--persist= flag keeps the notification on screen until manually dismissed. All page-me notifications should use =--persist= by default. +** Paging Craig's phone (away from the machine) + +The timed =notify= alarm above is the desktop channel. When Craig is away from the machine (or asks to be paged "on my phone"), use the agent pager instead — a Signal push to his phone from any machine or agent runtime: + +#+begin_src bash +agent-page "Build finished — ready for your eyes" + +# Timed phone page: same at-daemon pattern, different channel +echo "agent-page 'Meeting starts in 5'" | at 3:25pm +#+end_src + +Channel selection and the pager's mechanics live in protocols.org "Paging Craig — the agent pager". When in doubt, fire both: the desktop notification persists for whenever he returns, the phone push reaches him now. + ** Managing Alarms #+begin_src bash diff --git a/claude-templates/.ai/workflows/work-the-backlog.org b/claude-templates/.ai/workflows/work-the-backlog.org index 462e6b2..090841d 100644 --- a/claude-templates/.ai/workflows/work-the-backlog.org +++ b/claude-templates/.ai/workflows/work-the-backlog.org @@ -152,7 +152,7 @@ With paging on, fire one page when the set is done or the cap is hit — end-of- notify info "Page" "<project>: <N> done, <M> remaining — <one-line summary>" --persist #+end_src -=--persist= keeps it on screen until dismissed, and =info= is the page-me urgency convention (persistent but never crash-scary). The page fires when the set completes *or* the cap stops the run — either way exactly once. The message carries the project name, the completed count, and the remaining count (with skipped tasks noted in the run summary) so Craig can confirm ready and name the next project in one reply. There is no separate page-signal call — =notify= is the paging surface. +=--persist= keeps it on screen until dismissed, and =info= is the page-me urgency convention (persistent but never crash-scary). The page fires when the set completes *or* the cap stops the run — either way exactly once. The message carries the project name, the completed count, and the remaining count (with skipped tasks noted in the run summary) so Craig can confirm ready and name the next project in one reply. =notify= is the desktop paging surface; a run that expects Craig to be away also fires =agent-page= with the same message (the Signal phone channel — protocols.org "Paging Craig — the agent pager"). * Metrics diff --git a/claude-templates/bin/agent-page b/claude-templates/bin/agent-page new file mode 100755 index 0000000..11a5264 --- /dev/null +++ b/claude-templates/bin/agent-page @@ -0,0 +1,48 @@ +#!/bin/bash +# agent-page — page Craig's phone over Signal, from any machine or agent runtime. +# +# Usage: agent-page <message...> +# +# The pager identity (+15045173983) is registered in velox's signal-cli, so +# velox sends directly and every other machine ssh-relays the send over the +# tailnet. The recipient is Craig's Signal account UUID — his phone number +# reads as unregistered in Signal's directory, so never page the number. +# Verified end to end 2026-07-13 (phone push confirmed). +# +# This is the AWAY channel. At his desk, use the desktop channel instead: +# notify info "Title" "Message" --persist +# See protocols.org "Paging Craig" for choosing between them. +# +# Known caveats (tracked on the rulesets Signal-pager task): velox must be +# up and reachable on the tailnet, and the signal-cli account wants a +# periodic `receive` (staleness warnings appear otherwise). +# +# Source: ~/code/rulesets/claude-templates/bin/agent-page +# Install: make -C ~/code/rulesets install + +PAGER_ACCOUNT="+15045173983" +CRAIG_UUID="b1b5601e-6126-47f8-afaa-0a59f5188fde" +VELOX_HOST="velox.tailf3bb8c.ts.net" + +if [ $# -eq 0 ]; then + echo "usage: agent-page <message...>" >&2 + exit 2 +fi + +msg="$*" + +if [ "$(uname -n)" = "velox" ]; then + signal-cli -a "$PAGER_ACCOUNT" send -m "$msg" "$CRAIG_UUID" + rc=$? +else + # printf %q hardens the message for the remote shell. + ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \ + "$VELOX_HOST" \ + "signal-cli -a $PAGER_ACCOUNT send -m $(printf '%q' "$msg") $CRAIG_UUID" + rc=$? +fi + +if [ "$rc" -ne 0 ]; then + echo "agent-page: phone page failed (velox down or unreachable?) — fall back to the desktop channel: notify info 'Page' '<message>' --persist" >&2 +fi +exit "$rc" diff --git a/scripts/tests/agent-page.bats b/scripts/tests/agent-page.bats new file mode 100644 index 0000000..071e4b9 --- /dev/null +++ b/scripts/tests/agent-page.bats @@ -0,0 +1,68 @@ +#!/usr/bin/env bats +# agent-page — the runtime-neutral phone pager. Pages Craig over Signal from +# any machine on the tailnet: runs signal-cli directly on velox (where the +# pager identity lives), ssh-relays to velox from everywhere else. These tests +# stub ssh/uname/signal-cli on PATH to verify command construction without a +# network or a phone. + +setup() { + REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)" + PAGE="$REPO_ROOT/claude-templates/bin/agent-page" + STUBS="$(mktemp -d)" + LOG="$STUBS/calls.log" + cat > "$STUBS/ssh" <<EOF +#!/bin/bash +echo "ssh \$*" >> "$LOG" +exit 0 +EOF + cat > "$STUBS/signal-cli" <<EOF +#!/bin/bash +echo "signal-cli \$*" >> "$LOG" +exit 0 +EOF + chmod +x "$STUBS/ssh" "$STUBS/signal-cli" +} + +teardown() { + rm -rf "$STUBS" +} + +@test "no message exits 2 with usage" { + run bash "$PAGE" + [ "$status" -eq 2 ] + [[ "$output" == *"usage"* ]] +} + +@test "relays through ssh to velox with the pager account and Craig's UUID" { + PATH="$STUBS:$PATH" run bash "$PAGE" build finished + [ "$status" -eq 0 ] + grep -q "^ssh .*velox" "$LOG" + grep -q "15045173983" "$LOG" + grep -q "b1b5601e-6126-47f8-afaa-0a59f5188fde" "$LOG" + # printf %q escapes the space, so the relayed message reads build\ finished. + grep -qF 'build\ finished' "$LOG" +} + +@test "on velox itself, calls signal-cli directly (no ssh)" { + cat > "$STUBS/uname" <<'EOF' +#!/bin/bash +[ "$1" = "-n" ] && { echo velox; exit 0; } +exec /usr/bin/uname "$@" +EOF + chmod +x "$STUBS/uname" + PATH="$STUBS:$PATH" run bash "$PAGE" hello + [ "$status" -eq 0 ] + grep -q "^signal-cli " "$LOG" + ! grep -q "^ssh " "$LOG" +} + +@test "a failed relay reports the desktop fallback and propagates failure" { + cat > "$STUBS/ssh" <<'EOF' +#!/bin/bash +exit 255 +EOF + chmod +x "$STUBS/ssh" + PATH="$STUBS:$PATH" run bash "$PAGE" urgent thing + [ "$status" -ne 0 ] + [[ "$output" == *"notify"* ]] +} @@ -99,6 +99,12 @@ home confirmed (2026-07-11 reply) rulesets owns this task and the two-paths reco *** 2026-07-13 Mon @ 14:40:00 -0500 Added runtime-portability as a second motivation (Craig approved) The MCP portability inventory ([[file:docs/design/2026-07-13-runtime-portability-inventories.org]]) found signal-mcp exists only claude.ai-side — no local config anywhere — so a non-Claude agent (Codex-style or local LLM) has no paging path at all. The signal-cli runbook this task produces is therefore also the runtime-neutral page channel, not just home's replacement for ntfy. +*** 2026-07-13 Mon @ 18:30:19 -0500 agent-page shipped — every project now knows both channels +Craig's call: make paging universal and rename it the agent pager. NEW =claude-templates/bin/agent-page= (runs signal-cli directly on velox, ssh-relays from anywhere else, desktop-fallback hint on failure; 4 bats tests; live-verified from ratio through the real script). protocols.org "Paging Craig" rewritten around the two channels (notify desktop + agent-page phone; signal-mcp demoted to a velox-local nicety); page-me.org gained the phone section + fire-both guidance; work-the-backlog's end-of-set page names both surfaces; INDEX updated. Every project inherits via the startup sync + make install. Remaining here: the runbook proper, the receive timer, ssh-only vs linked-device. + +*** 2026-07-13 Mon @ 18:13:07 -0500 RECONCILED — one channel, on velox; live page verified end to end +The two-paths question is answered: there is ONE pager identity, +15045173983, registered in velox's signal-cli (account file 465310) — and signal-mcp is a locally-configured MCP server in velox's global ~/.claude.json (not claude.ai-side as the 14:40 entry inferred; it's just invisible from ratio, which is why home and this session couldn't find it). ratio's signal-cli holds only Craig's personal number (note-to-self, no push). Verified live today: =ssh velox 'signal-cli -a +15045173983 send -m … b1b5601e-6126-47f8-afaa-0a59f5188fde'= buzzed Craig's phone — his remembered CLI page was this same account on velox. Reliability findings for the runbook: both accounts throw receive-staleness warnings (velox 40 days, ratio 26; the signal protocol wants regular receives — a systemd receive timer on velox is the roam-sync-shaped fix), and the channel requires velox to be up. Remaining deliverables sharpened: the runbook (send + read-replies + receive timer + account notes); decide ssh-over-tailnet-only vs registering ratio as a linked device of the pager account; update protocols.org "Paging Craig" (it names signal-mcp as the only supported path — true only on velox; the ssh recipe is the cross-machine path) — shared-asset edit, own review pass. Interim recipe sent to home so it's unblocked today. + ** TODO [#C] KB orphan-node review pass :chore: :PROPERTIES: :CREATED: [2026-07-01 Wed] |
