mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-22 12:42:22 +02:00
Six of the eleven defects here were in the MEASUREMENT, not the product, and they were wrong in both directions. Harness, all of which silently produced wrong numbers: - coverage.py preflight refused every sweep on a box holding exactly one backend: stack.sh's supervisor is a `bash -c` quoting the whole uvicorn line, so it carries both "-m uvicorn backend.main" AND the venv python path. Discriminate on POSITION. - stack.sh status reported 2 backends over 1 and 0 webpack over a live dev server (webpack retitles its process). A status check whose job is preventing a second stack, failing in the direction that lets one land. - c7_run.sh/c8_run.sh slice r6_be.log while stack.sh names logs by TAG: a stack under any other tag hands every trial an empty slice and the sweep reports a confident 0/108. Now refuses loudly; it caught this exact mistake on first use. - "Browser command timed out" was bucketed infra. It is ONE command blowing its own budget, not a dead webview: all 4 such rows were BrowserFindComposer at exactly its 30s cap, every run completed after, zero card-gone markers in the whole log. Filed as infra it read as 11.8% flake AND lifted holdout reach 70% -> 84%. - api_retry / rate_limit_error now grade as infra. A provider 429 storm turned clean 15-21s exclusions into 188s product_no_composer rows. - bench.py prints reach BOTH ways when a row is UNVERIFIED. An exclusion resting on the agent's own word quietly flatters the score, and coverage.py's own instruction to confirm it by hand goes unread (I quoted a 100% that excluded onlinegdb). Timing was measuring 0.2% of the run: prestage completes BEFORE metrics_started_at, so other_ms was 25ms of a 12700ms median while prestage (4146ms, ~61%) sat in no bucket at all. prestage_ms/task_ms are now recorded; total_ms is deliberately NOT redefined, which would invalidate every before/after already taken against it. Product: - find_composer rejected ACE/CodeMirror-5/Monaco composers. Their input is a ~1x1 offscreen textarea that paints into a sibling div, so it can never pass a size gate. Accept it when a VISIBLE ancestor is composer-sized; honeypots stay out because the input itself must not be display:none/visibility:hidden/opacity:0. anon reach 80% -> 100%, holdout 89% -> 90%, p95 38.6s -> 9.7s. - the composer poll slept a blind 0+1.2+1.4 = 2.6s whenever prestage staged nothing, which is nearly every run, and it was the whole of other_ms's suspicious constancy (2610-2613ms regardless of tools_ms). Stop when two reads are identical, the rule the opener poll 40 lines below already applies. other_ms -53.7%, tools_ms flat. - prestage no longer navigates to the page it is already on, nor sleeps 0.35s before its first settle probe. - is_replay_boundary reasoned from the NAME alone, so x.com's composer textbox named "Post text" was ruled an irreversible send and truncated its replay to a bare navigate. Excluded by ROLE; first_unsafe_step now passes role through at all. Measured on this box: reach 100% (83% if onlinegdb's unverified exclusion is bogus), 0 false successes in ~155 runs, prestage tier-0/1 2702ms, other_ms -53.7%, infra flake 0/158, holdout 18/20. Criteria 2/4/9 need live writes and are untouched. Full evidence, including what did NOT work, in e2e/browser-v3/RESULTS_2026-08-06.md. Also drops the tracked electron/node_modules symlink pointing at another machine's Downloads folder; it is dangling on every other checkout and re-breaks the install on any stash or checkout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
138 lines
6.4 KiB
Bash
Executable File
138 lines
6.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# The isolated v3 measurement stack: backend :8326, webpack :3026, Electron on its own profile.
|
|
#
|
|
# One script because rebuilding it by hand three times cost three different half-booted stacks, and a
|
|
# half-booted stack does not fail loudly, it just measures nothing and blames the product.
|
|
#
|
|
# ./stack.sh up dry backend refuses the irreversible click (coverage sweeps)
|
|
# ./stack.sh up live backend really clicks send (canary write tests)
|
|
# ./stack.sh down everything, SIGTERM then SIGKILL, ports verified free
|
|
# ./stack.sh status what is up right now
|
|
#
|
|
# Never touches :8324 / :3000. Those belong to whatever else is on this box.
|
|
|
|
# Where logs, profiles and run output go. Defaults to runs/ beside this harness; override with
|
|
# OSW_BENCH_DIR to keep multi-gigabyte browser profiles off the repo disk.
|
|
SP="${OSW_BENCH_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/runs}"
|
|
mkdir -p "$SP"
|
|
# The harness itself lives beside this script; SP is only for run OUTPUT.
|
|
HARNESS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
TREE="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
TAG="${TAG:-run}"
|
|
|
|
status() {
|
|
# Two bugs lived in the first version of these four lines, and both printed a reassuring 0 over a
|
|
# fully live stack. macOS pgrep has no -c flag at all, so `pgrep -fc` exits on a usage error and
|
|
# the count renders empty; and the patterns were the words I type rather than the words ps prints
|
|
# (uvicorn runs as `python -m uvicorn`). A status check that reads clean while the box is busy is
|
|
# the worst instrument in this directory, because its whole job is to stop a second stack landing
|
|
# on the first. Count with grep on real ps output.
|
|
echo "=== procs ==="
|
|
local ps_out
|
|
ps_out="$(ps -Ao pid,command)"
|
|
# The backend pattern is anchored on POSITION, not just the words: this script's own supervisor is
|
|
# a `bash -c` whose command line quotes the whole uvicorn invocation, so a bare match on
|
|
# "-m uvicorn backend.main" counts the supervisor as a second backend and reports 2 on a box
|
|
# holding exactly 1. Overcounting here is the same class of lie as the pgrep -fc bug above, and it
|
|
# sends you hunting a stack that does not exist. The interpreter's line starts with the python
|
|
# binary right after the pid; the supervisor's starts with bash.
|
|
for entry in "\-m uvicorn backend.main|^ *[0-9]+ +[^ ]*python[^ ]* +-m uvicorn backend\.main" \
|
|
"bin/webpack|(bin/webpack|^ *[0-9]+ +webpack\b)" "MacOS/Electron|MacOS/Electron" \
|
|
"keep_renderer|keep_renderer" "bench.py|bench\.py" "browser_canary|browser_canary"; do
|
|
label="${entry%%|*}"; pat="${entry#*|}"
|
|
printf " %-34s %s\n" "$label" "$(echo "$ps_out" | grep -cE "$pat")"
|
|
done
|
|
echo "=== ports ==="
|
|
for pt in 8324 8326 3000 3026 20128; do
|
|
printf " %-6s %s\n" "$pt" "$(lsof -ti tcp:$pt 2>/dev/null | tr '\n' ' ')"
|
|
done
|
|
# Anything on :8324 is the OTHER checkout. Say so out loud: while it is up, the single 9router on
|
|
# :20128 is contended and every timing this stack produces is noise (a sweep once scored 1/9 vs
|
|
# 4/9 with zero code change, purely on that contention).
|
|
if [ -n "$(lsof -ti tcp:8324 2>/dev/null)" ]; then
|
|
echo " !! another OpenSwarm is on :8324. Do NOT measure, and do NOT kill it."
|
|
fi
|
|
}
|
|
|
|
down() {
|
|
# SCOPED TO THIS STACK ONLY. The first version matched on `uvicorn backend.main`, which is exactly
|
|
# what the OTHER OpenSwarm checkout on this box runs too: one careless `stack.sh down` would have
|
|
# killed a colleague's backend on :8324 mid-session. Nothing here may match a process this script
|
|
# did not start, so identify them by MY ports and MY profile directory, never by a generic name.
|
|
pkill -f "keep_renderer" 2>/dev/null
|
|
pkill -f "BACKEND RESTARTED" 2>/dev/null
|
|
pkill -f "user-data-dir=$SP/udd" 2>/dev/null
|
|
pkill -f "browser_canary" 2>/dev/null
|
|
pkill -f "$HARNESS/bench.py" 2>/dev/null
|
|
sleep 3
|
|
for pt in 8326 3026; do
|
|
pids=$(lsof -ti tcp:$pt 2>/dev/null)
|
|
[ -n "$pids" ] && kill -9 $pids 2>/dev/null
|
|
done
|
|
pkill -9 -f "user-data-dir=$SP/udd" 2>/dev/null
|
|
sleep 1
|
|
status
|
|
}
|
|
|
|
up() {
|
|
local mode="${1:-dry}"
|
|
local dry=0
|
|
[ "$mode" = "dry" ] && dry=1
|
|
|
|
# A stale stack under a fresh one is the single most expensive failure here: two backends fight
|
|
# over the one 9router and every number becomes a coin flip. Always start from nothing.
|
|
down >/dev/null 2>&1
|
|
|
|
# Supervised, because the backend has died mid-measurement on a clean SIGTERM with no error in
|
|
# its log (2026-08-05 07:44, 52 minutes in, no other stack on the box). An unsupervised death does
|
|
# not announce itself: the harness just starts recording connection errors as product failures.
|
|
# The restart marker goes into the same log the harness slices, so any trial that spans a restart
|
|
# can be excluded instead of counted.
|
|
cd "$TREE" || exit 1
|
|
nohup bash -c '
|
|
while true; do
|
|
OPENSWARM_PORT=8326 OSW_SENDSCRIPT_DRYRUN='"$dry"' OPENSWARM_DEV=1 \
|
|
./backend/.venv/bin/python -m uvicorn backend.main:app --port 8326 --host 127.0.0.1 \
|
|
>> "'"$SP/${TAG}_be.log"'" 2>&1
|
|
echo "[stack] BACKEND RESTARTED at $(date +%H:%M:%S)" >> "'"$SP/${TAG}_be.log"'"
|
|
sleep 4
|
|
done' > /dev/null 2>&1 &
|
|
disown
|
|
|
|
cd "$TREE/frontend" || exit 1
|
|
OPENSWARM_DEV_PORT=3026 OPENSWARM_PORT=8326 \
|
|
./node_modules/.bin/webpack serve --mode development \
|
|
> "$SP/${TAG}_wp.log" 2>&1 &
|
|
|
|
# A real authenticated 200, not just "something accepted a TCP connection". /api/health does not
|
|
# exist (it 404s), and curl calls a 404 a success, so the old check passed the instant the socket
|
|
# opened and handed the next step a backend that had not finished booting.
|
|
echo "waiting for backend :8326 ..."
|
|
for i in $(seq 1 90); do
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 \
|
|
-H "Authorization: Bearer $(cat "$TREE/backend/data/auth.token" 2>/dev/null)" \
|
|
"http://127.0.0.1:8326/api/dashboards/list")
|
|
[ "$code" = "200" ] && break
|
|
sleep 2
|
|
done
|
|
echo "waiting for webpack :3026 ..."
|
|
for i in $(seq 1 120); do
|
|
curl -s -o /dev/null --max-time 3 "http://localhost:3026" && break
|
|
sleep 2
|
|
done
|
|
|
|
OPENSWARM_DEV_PORT=3026 OPENSWARM_PORT=8326 nohup "$HARNESS/keep_renderer.sh" \
|
|
>> "$SP/renderer.log" 2>&1 &
|
|
disown
|
|
sleep 25
|
|
echo "mode=$mode (OSW_SENDSCRIPT_DRYRUN=$dry) tag=$TAG"
|
|
status
|
|
}
|
|
|
|
case "$1" in
|
|
up) up "$2" ;;
|
|
down) down ;;
|
|
status) status ;;
|
|
*) echo "usage: stack.sh {up dry|up live|down|status}"; exit 1 ;;
|
|
esac
|