diff --git a/apps/ambient-inventory-agent/README.md b/apps/ambient-inventory-agent/README.md index b6c0bb4..4883b53 100644 --- a/apps/ambient-inventory-agent/README.md +++ b/apps/ambient-inventory-agent/README.md @@ -154,6 +154,12 @@ or instance role). ### Run +```bash +./setup_demo.sh # reseeds, then runs the app on :8008 +``` + +Equivalent by hand, in this order — the reseed has to land while the server is down: + ```bash python seed_demo.py --reset uvicorn app.main:app --port 8008 @@ -166,21 +172,26 @@ the first chat message from stalling on stage. Check it worked: curl -s localhost:8008/api/health | python -m json.tool ``` -`mcp.ready` must be `true`. Then open `http://localhost:8008/?fresh=1` for a -clean run — it resets the scenario and starts a new delayed alert. The `fresh=1` -parameter is stripped from the URL immediately, so an accidental refresh -mid-demo will not wipe the scenario. +`mcp.ready` must be `true`. Then open `http://localhost:8008/` and press **Start +demo** when you begin. + +Reseeding is a pre-flight step, not something the page does, which is why +`setup_demo.sh` does it before starting the server. Neither loading the page nor +pressing **Start demo** reseeds — `/api/demo/start` mints a new `session_id`, which +leaves the previous run's alert and transcript behind but does *not* clear +`purchase_orders`. That matters, because a leftover order for the shared component +makes the next sweep decide no alert is needed, so run `./setup_demo.sh` again +between runs. Refreshing mid-demo is safe. Avoid `--reload` during a rehearsal: the pending alert lives in an in-process task and a reload cancels it. ## Stage flow -Seed first, then open the app and walk away: +Start it, then open the app and walk away: ```bash -python seed_demo.py --reset -uvicorn app.main:app --port 8008 # wait for [mcp] connected +./setup_demo.sh # wait for [mcp] connected open http://localhost:8008/ ``` diff --git a/apps/ambient-inventory-agent/setup_demo.sh b/apps/ambient-inventory-agent/setup_demo.sh new file mode 100755 index 0000000..9b26818 --- /dev/null +++ b/apps/ambient-inventory-agent/setup_demo.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# +# Reseed, then run the app on :8008. Ctrl+C stops it. +# +# ./setup_demo.sh +# +# Reseeding every run is required: approving the order writes a purchase_orders doc for +# the shared component, and the next sweep skips the alert because an order is already +# inbound. Pressing "Start demo" in the UI does not reseed. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")" + +PY=${PY:-.venv/bin/python} +[[ -x "$PY" ]] || PY=python3 + +[[ -n "${1:-}" ]] && { echo "ERROR: unknown option '$1' (takes none)" >&2; exit 1; } + +echo "==> Reseeding the demo database" +"$PY" seed_demo.py --reset + +echo "==> App: http://localhost:8008/ (wait for '[mcp] connected'; Ctrl+C to stop)" +exec "$PY" -m uvicorn app.main:app --port 8008 diff --git a/apps/remote-mcp-perf-triage/README.md b/apps/remote-mcp-perf-triage/README.md index fdba1c0..bf02878 100644 --- a/apps/remote-mcp-perf-triage/README.md +++ b/apps/remote-mcp-perf-triage/README.md @@ -53,6 +53,8 @@ would normally require a developer who knows exactly where to look. | File | Purpose | |------|---------| +| `setup_demo.sh` | Resets the demo, then runs the checkout page. `--drop` for a full reseed. | +| `trickle.sh` | `start`/`stop`/`status` for the Performance Advisor trickle. | | `checkout_app.py` | The Leafy Electronics checkout page. Really hangs on the slow poll, then fires the incident to ChatGPT. | | `seed_payments.py` | Seeds a large, realistic `payments` collection (no index on `session_id`); `--drop-index` resets the demo. | | `generate_load.py` | Runs the checkout status-poll query repeatedly to feed Performance Advisor. | @@ -144,7 +146,7 @@ serves a Leafy Electronics checkout that runs the **real** status-poll query aga unindexed collection — so the hang isn't staged, it's the bug: ```bash -python checkout_app.py # http://127.0.0.1:8000 +./setup_demo.sh # resets, then serves http://127.0.0.1:8000 python checkout_app.py --no-incident # rehearse without paging the agent ``` @@ -245,19 +247,35 @@ completes sub-millisecond — well under any checkout timeout. ## Reset (to re-run the demo) +`setup_demo.sh` does the reset and then runs the checkout page, so one command per +demo run is enough: + +```bash +./setup_demo.sh # light reset: drops the index, keeps the 300k docs +./setup_demo.sh --drop # full reseed: fresh 300k, several minutes +``` + +The trickle is managed separately, since it runs for hours across many demo runs: + +```bash +./trickle.sh start # kills any existing trickle, then starts one fresh +./trickle.sh status +./trickle.sh stop # end of day +``` + ### Light reset (usual case) -If the collection is intact and you just created the index during the demo, drop the -index so the slow condition returns: +If the collection is intact and you just created the index during the demo, dropping +the index restores the slow condition — that is all `./setup_demo.sh` does. By hand: ```bash python seed_payments.py --drop-index # drops the index, keeps the 300k docs ``` -This exits before the insert loop, so your data is untouched. (Equivalent by hand: +This exits before the insert loop, so your data is untouched. (Equivalent in the shell: `db.payments.dropIndex("session_id_1_status_1")`.) -Then make sure the trickle is running again ahead of the next run: +Keep the trickle running ahead of the next run — `./trickle.sh start`, or by hand: ```bash nohup python generate_load.py > load.log 2>&1 & # background; tail -f load.log to watch @@ -265,7 +283,7 @@ nohup python generate_load.py > load.log 2>&1 & # background; tail -f load.log ### Full reseed ritual (fresh data) -If you need to rebuild the collection from scratch: +To rebuild the collection from scratch, `./setup_demo.sh --drop`. By hand: ```bash python seed_payments.py --drop # drop, then seed a fresh 300k diff --git a/apps/remote-mcp-perf-triage/setup_demo.sh b/apps/remote-mcp-perf-triage/setup_demo.sh new file mode 100755 index 0000000..eedc55f --- /dev/null +++ b/apps/remote-mcp-perf-triage/setup_demo.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# +# Reset, then run the checkout page on :8000. Ctrl+C stops it. +# +# ./setup_demo.sh # drop the index, keep the 300k docs (usual case) +# ./setup_demo.sh --drop # full reseed: fresh 300k, several minutes +# +# Dropping the index the agent created is what restores the slow COLLSCAN. The documents +# stay because nothing in the demo mutates them. +# +# The Advisor trickle is separate: ./trickle.sh start, well before showtime. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")" + +PY=${PY:-.venv/bin/python} +[[ -x "$PY" ]] || PY=python3 + +case "${1:-}" in + "") echo "==> Dropping the demo index (restores the slow COLLSCAN)" + "$PY" seed_payments.py --drop-index ;; + --drop) echo "==> Full reseed — several minutes" + "$PY" seed_payments.py --drop + echo "! Cache is cold and the Advisor recommendation reset; allow ~15-30 min of trickle" ;; + *) echo "ERROR: unknown option '$1' (only --drop)" >&2; exit 1 ;; +esac + +pgrep -f 'generate_load\.py' >/dev/null \ + || echo "! Advisor trickle NOT running — ./trickle.sh start (allow ~15-30 min)" + +echo "==> Checkout page: http://127.0.0.1:8000 (Ctrl+C to stop)" +exec "$PY" checkout_app.py diff --git a/apps/remote-mcp-perf-triage/trickle.sh b/apps/remote-mcp-perf-triage/trickle.sh new file mode 100755 index 0000000..526e83c --- /dev/null +++ b/apps/remote-mcp-perf-triage/trickle.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# +# The Performance Advisor trickle — generate_load.py, backgrounded. +# +# ./trickle.sh start # kills any existing trickle, then starts one fresh +# ./trickle.sh stop +# ./trickle.sh status +# +# Separate from setup because it runs on a different clock: the Advisor takes ~15-30 min +# to first surface its recommendation, and only holds it while slow queries keep recurring +# AND the index is still absent. Start it well before showtime and leave it up across demo +# runs. Stop it at end of day. +# +# Watch it with: tail -f load.log +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")" + +PY=${PY:-.venv/bin/python} +[[ -x "$PY" ]] || PY=python3 + +# pgrep exits 1 with no matches, which would abort the script under `set -e`. +pids() { pgrep -f 'generate_load\.py' | tr '\n' ' ' || true; } + +kill_all() { + local p + p=$(pids) + [[ -z "$p" ]] && return 0 + echo "Killing existing trickle: $p" + kill $p 2>/dev/null || true + sleep 2 + p=$(pids) + [[ -n "$p" ]] && kill -9 $p 2>/dev/null || true + return 0 +} + +case "${1:-}" in + start) + kill_all + nohup "$PY" generate_load.py >>load.log 2>&1 & + pid=$! + sleep 2 + if ! kill -0 "$pid" 2>/dev/null; then + echo "ERROR: exited immediately — last lines of load.log:" >&2 + tail -5 load.log >&2 + exit 1 + fi + echo "Started (pid $pid). Allow ~15-30 min, then confirm with" + echo "atlas-get-performance-advisor: expect { session_id: 1, status: 1 } on ecommerce.payments" + ;; + stop) + [[ -z "$(pids)" ]] && { echo "Not running"; exit 0; } + kill_all + echo "Stopped" + ;; + status) + p=$(pids) + if [[ -z "$p" ]]; then + echo "NOT running — the Advisor recommendation will go stale. ./trickle.sh start" + exit 1 + fi + # Uptime is the thing that matters: the Advisor cares how long this has been + # running, not that it is alive this second. + ps -o pid=,lstart=,args= -p $p | sed 's/^ *//' + # Plain `if`, not a trailing `&&` — as the last command in this branch a false test + # would become the exit status and report a healthy trickle as a failure. + if [[ -f load.log ]]; then + tail -3 load.log | sed 's/^/ /' + fi + ;; + *) + echo "Usage: ./trickle.sh {start|stop|status}" >&2 + exit 1 + ;; +esac