754e769f2f
W31 wanted a relay mode so the human stops being the router. @reviewer's design pass rejected it, and the rejection is the load-bearing part: @dispatcher is itself PRIVILEGED and in TRUST, so relaying would turn a single-keyword regex hit into an authenticated order every worker accepts — a confused deputy. Identity has no good answer either: posting as `dispatcher` breaks in_reply_to for the real asker and turns a stateless one-shot into an orchestrator; posting as the asker means forging `from` and signing the forgery with the room secret, which is the exact attack test_post_secret_gate.py exists to stop. Opt-in relay keeps all of that plumbing and discards the checkpoint precisely when it is needed, since "relay this" is said before the route is seen. The problem worth solving was never "the human types too much" — it is that a rich agent sometimes IMPROVISES instead of running the command it was handed. That is a compliance defect at the CALLER, so it is fixed at the caller. `bp --route "<request>"` asks @dispatcher, PRINTS the route, then runs it here as the caller: TRUST, verified and reply-routing all unchanged, and the inspect-before-work checkpoint survives because the route is printed first. @herder and @reviewer are excluded from auto-run — live host reach and real money per call must still pass a human. An ambiguous answer (the roster) stops with a non-zero exit rather than picking blindly. Also stored the matching lesson in /bullpen/lessons-learned (id 228), which the lurker already prepends to every poke: run what @dispatcher hands you, do not improvise. tests/test_bp_route.py pins the three properties that separate this from a relay — route shown before execution, caller's own nick on the ask, heavyweight workers not auto-run — plus non-interference with plain `bp`. 6/6.
75 lines
3.9 KiB
Bash
Executable File
75 lines
3.9 KiB
Bash
Executable File
#!/bin/sh
|
|
# bp — REPL shorthand for the bullpen room. Expands to `room-ask`.
|
|
# bp <worker> <task...> ask a worker (@/# prefix optional), block for the reply
|
|
# bp --from NICK <worker> <task> set your nick (default: $BP_NICK, else markus)
|
|
# bp --timeout N <worker> <task> override the wait (researcher auto-bumps to 200s)
|
|
# bp --route <task...> ask @dispatcher who to use, then RUN it as yourself
|
|
# bp show the roster (@dispatcher)
|
|
# Over sic a rich agent calls: sic hertz bp --from noether researcher "..."
|
|
#
|
|
# --route exists INSTEAD of a relay mode in @dispatcher. The room contract is deliberately
|
|
# "reply-only, NEVER relays" (bin/bullpen-dispatcher): a misroute costs one wasted hop rather
|
|
# than a wrong answer stated as fact. @reviewer's design pass 2026-08-02 found a relay would be
|
|
# strictly worse than that, not better — @dispatcher is itself PRIVILEGED and in TRUST, so
|
|
# relaying turns a single-keyword regex hit into an authenticated order every worker accepts
|
|
# (confused deputy), and it has no good answer for identity: posting as `dispatcher` breaks
|
|
# in_reply_to for the real asker, posting as the asker means forging `from` and signing the
|
|
# forgery with the room secret — the exact attack the post-secret gate exists to stop.
|
|
#
|
|
# The problem worth solving was never "the human types too much"; it is that a rich agent
|
|
# sometimes IMPROVISES instead of running the command @dispatcher handed back. That is a
|
|
# compliance defect at the CALLER, so it is fixed at the caller: --route asks, SHOWS the route,
|
|
# and runs it here, as you, with your nick — TRUST, verified and reply-routing all unchanged,
|
|
# and the inspect-before-work checkpoint survives because the route is printed first.
|
|
# Heavyweight workers are NOT auto-run: @herder reaches live hosts and @reviewer costs real
|
|
# money per call, so a misroute there must still pass a human before it fires.
|
|
FROM="${BP_NICK:-markus}"
|
|
TIMEOUT=""
|
|
ROUTE=0
|
|
while [ "${1:-}" = "--from" ] || [ "${1:-}" = "--timeout" ] || [ "${1:-}" = "--route" ]; do
|
|
case "$1" in
|
|
--from) FROM="$2"; shift 2 ;;
|
|
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
--route) ROUTE=1; shift ;;
|
|
esac
|
|
done
|
|
if [ $# -eq 0 ]; then exec bullpen-dispatcher --roster; fi
|
|
|
|
# Workers a misroute must never fire unattended: live fleet reach, or real money per call.
|
|
NO_AUTORUN="herder reviewer"
|
|
|
|
pick_timeout() {
|
|
case "$1" in researcher) echo 200 ;; herder) echo 280 ;; testdesigner) echo 340 ;;
|
|
py) echo 340 ;; foreman) echo 1500 ;; reviewer) echo 600 ;; *) echo 60 ;; esac
|
|
}
|
|
|
|
if [ "$ROUTE" = "1" ]; then
|
|
ROUTING=$(bullpen-dispatcher --once "$*" 2>/dev/null)
|
|
[ -n "$ROUTING" ] || { echo "bp --route: @dispatcher returned nothing" >&2; exit 1; }
|
|
printf '%s\n' "$ROUTING"
|
|
# _route_line's format is machine-readable on purpose: "→ @nick — description."
|
|
WORKER=$(printf '%s' "$ROUTING" | sed -n 's/^→ @\([A-Za-z0-9_-]*\).*/\1/p' | head -1)
|
|
if [ -z "$WORKER" ]; then
|
|
echo "" >&2
|
|
echo "bp --route: no single worker named — the roster above IS the answer." >&2
|
|
echo " Ambiguity is a real result here; pick one and run bp <worker> \"…\"." >&2
|
|
exit 2
|
|
fi
|
|
for w in $NO_AUTORUN; do
|
|
if [ "$w" = "$WORKER" ]; then
|
|
echo "" >&2
|
|
echo "bp --route: @$WORKER is heavyweight (live host reach / real cost) — NOT auto-run." >&2
|
|
echo " Read the route above, then run it deliberately." >&2
|
|
exit 3
|
|
fi
|
|
done
|
|
echo ""
|
|
echo "bp --route: running it as @$FROM …"
|
|
[ -n "$TIMEOUT" ] || TIMEOUT=$(pick_timeout "$WORKER")
|
|
exec room-ask --from "$FROM" --timeout "$TIMEOUT" "@$WORKER" "$*"
|
|
fi
|
|
|
|
WORKER=$(printf '%s' "$1" | sed 's/^[@#]//'); shift
|
|
[ -n "$TIMEOUT" ] || TIMEOUT=$(pick_timeout "$WORKER")
|
|
exec room-ask --from "$FROM" --timeout "$TIMEOUT" "@$WORKER" "$*"
|