#!/usr/bin/env bash # # workflow-loop.sh — self-contained headless test for the Buzz workflow loop. # # Buzz's *source-confirmed* structured-work + human-approval primitive (kinds # 30620 def / 46020 trigger / 46030-46031 grant-deny / 46001-46007 lifecycle), # driven end to end through an embedded relay (`amy serve`, i.e. geode — no # external binary). Three `amy` accounts: # # alice = requester (triggers the workflow, kind-46020) # bot = the runner (`amy buzz workflow run`; does agent work, posts the # 46010 approval gate, then on grant runs --on-approve → 46005) # carol = the approver (publishes 46030 grant / 46031 deny) # # Because the run id IS the trigger event id (and doubles as the approval # token), carol can grant/deny without any extra token bookkeeping. # # 1. alice triggers → run_id # 2. bot run --once → agent work → 46010 gate (state=awaiting_approval) # 3. carol approves → 46030 (d = run_id) # 4. bot run --once → resolves the gate → --on-approve → 46005 completed # 5. alice show → WorkflowRunAggregator folds it to state=completed + PR # 6. deny path: a second run, carol denies → state=denied (work discarded) # # Usage: ./workflow-loop.sh [--port N] [--no-build] # set -uo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../.." && pwd)" STATE_DIR="$SCRIPT_DIR/state-workflow-loop" AMY_BIN="$REPO_ROOT/cli/build/install/amy/bin/amy" PORT=7788 BUILD=1 while [[ $# -gt 0 ]]; do case "$1" in --port) PORT="$2"; shift 2 ;; --no-build) BUILD=0; shift ;; *) echo "unknown arg: $1" >&2; exit 2 ;; esac done RELAY="ws://127.0.0.1:$PORT" CHANNEL="3f2504e0-4f89-41d3-9a0c-0305e82c3301" WFID="agent-build" if [[ $BUILD -eq 1 ]]; then echo "> building amy…" >&2 (cd "$REPO_ROOT" && ./gradlew -q :cli:installDist) || { echo "build failed" >&2; exit 1; } fi [[ -x "$AMY_BIN" ]] || { echo "amy not built at $AMY_BIN (drop --no-build)" >&2; exit 1; } rm -rf "$STATE_DIR"; mkdir -p "$STATE_DIR" REQ_HOME="$STATE_DIR/alice"; BOT_HOME="$STATE_DIR/bot"; APV_HOME="$STATE_DIR/carol" mkdir -p "$REQ_HOME" "$BOT_HOME" "$APV_HOME" RELAY_LOG="$STATE_DIR/relay.log" PASS=0; FAIL=0 RELAY_PID="" cleanup() { [[ -n "$RELAY_PID" ]] && kill "$RELAY_PID" 2>/dev/null; } trap cleanup EXIT run_req() { HOME="$REQ_HOME" "$AMY_BIN" --account alice --secret-backend plaintext --json "$@" 2>/dev/null; } run_bot() { HOME="$BOT_HOME" "$AMY_BIN" --account bot --secret-backend plaintext --json "$@" 2>/dev/null; } run_apv() { HOME="$APV_HOME" "$AMY_BIN" --account carol --secret-backend plaintext --json "$@" 2>/dev/null; } jkey() { python3 -c ' import sys, json v = json.load(sys.stdin).get("'"$1"'", "") print(str(v).lower() if isinstance(v, bool) else v)' } check() { # check