Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .assets-revision
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ site.recreation_gov: 00faedd20a96b4b10d5013a1c07ace9472d7ee80

# BoardGameGeek HF PR #25: immutable reviewed bundle; preserve all other pins.
site.boardgamegeek: 6737719a7eeb8d9b5fba77072d455e0d9afd2a93

# CarMax original asset PR #15, immutable reviewed seed and stock imagery.
site.carmax: 189b9cdcfd41a21b99ce4797f3bec152d8564a7f
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Force LF line endings for shell scripts so they execute in Linux containers
# even when checked out on Windows.
*.sh text eol=lf
*.bash text eol=lf
Dockerfile text eol=lf
12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A coding agent (Claude Code, Cursor, Aider, Codex, ...) is reading this. Read on

## What it is

37 Flask mirror websites (Amazon, GitHub, BBC News, ...) packaged into one Docker image, plus a control plane on `:8101` for resetting per-site state. Used as a deterministic offline environment for web-agent benchmarks. ~3 GB image.
38 Flask mirror websites (Amazon, GitHub, BBC News, ...) packaged into one Docker image, plus a control plane on `:8101` for resetting per-site state. Used as a deterministic offline environment for web-agent benchmarks. ~3 GB image.

Two repos:
- **code** (this one) — Flask apps, control plane, scripts.
Expand Down Expand Up @@ -48,17 +48,17 @@ Inside the image, sites live at `/opt/WebSyn/<site>/`. The path predates the ren
# fresh clone
./scripts/fetch_assets.sh # pulls assets from HF
./scripts/build.sh # docker build -t webharbor:dev .
docker run -d -p 8101:8101 -p 40000-40036:40000-40036 webharbor:dev
docker run -d -p 8101:8101 -p 40000-40037:40000-40037 webharbor:dev
```

Or use the published image directly:

```bash
docker run -d -p 8101:8101 -p 40000-40036:40000-40036 \
docker run -d -p 8101:8101 -p 40000-40037:40000-40037 \
battalion7244/webharbor:latest
```

Sites are on `40000`-`40036` in the order declared by `SITES=( ... )` in `websyn_start.sh`. Control plane:
Sites are on `40000`-`40037` in the order declared by `SITES=( ... )` in `websyn_start.sh`. Control plane:

| Method | Path | Purpose |
|--------|---------------------|-------------------------------------------|
Expand Down Expand Up @@ -136,13 +136,13 @@ python3 -m py_compile sites/<site>/app.py

# 3. run on alt ports (don't collide with anything you already have running)
docker run -d --rm --name wh-test \
-p 8201:8101 -p 41000-41036:40000-40036 webharbor:dev
-p 8201:8101 -p 41000-41037:40000-40037 webharbor:dev

# 4. control plane healthy, all sites alive
curl -s http://localhost:8201/health | python3 -m json.tool | head

# 5. every site renders 200
for p in $(seq 41000 41036); do
for p in $(seq 41000 41037); do
curl -so /dev/null -w "$p:%{http_code}\n" http://localhost:$p/
done

Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# WebHarbor — slim, self-contained image.
# 37 Flask mirror sites + control plane on :8101.
# 38 Flask mirror sites + control plane on :8101.

FROM python:3.12-slim-bookworm

Expand Down Expand Up @@ -125,6 +125,6 @@ print('AccuWeather seed DB generated at build time.')" && rm -rf /opt/WebSyn/acc
# Upgrade the pinned Recreation.gov seed before it becomes the reset fixture.
RUN cd /opt/WebSyn/recreation_gov && python3 migrate_seed.py

EXPOSE 8101 40000-40036
EXPOSE 8101 40000-40037

CMD ["/opt/websyn_start.sh"]
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ WebHarbor takes a different approach. We leverage coding agent (e.g., Claude Cod
- **Deep features unlocked** — carts, checkouts, accounts, all fully testable
- **Evolving** — harder tasks drive richer mirrors; the environment grows with agents
- **RL-ready** — sub-second database resets between rollouts
- **Community-driven** — 37 sites today, scaling to 100+ together
- **Community-driven** — 38 sites today, scaling to 100+ together

## 🚀 Quickstart

Build this checkout to run its registered web environments (published image tags may have an older registry):

```bash
./scripts/build.sh webharbor:dev
docker run -p 8101:8101 -p 40000-40036:40000-40036 webharbor:dev
docker run -p 8101:8101 -p 40000-40037:40000-40037 webharbor:dev
```

Then point your agent at `http://localhost:40000` through `http://localhost:40036` to explore 37 local mirrors of WebVoyager sites: `Allrecipes, Amazon, Apple, ArXiv, BBC News, Booking, GitHub, Google Flights, Google Maps, Google Search, Hugging Face, Wolfram Alpha, Cambridge Dictionary, Coursera, ESPN, Merriam-Webster, IKEA, Phys.org, Target, TED, Ohio State University, Rotten Tomatoes, Compass, Walmart Careers, FedEx, WebMD Doctor, Healthline, Kaggle, NVIDIA, UC Berkeley, B&H Photo, AccuWeather, GOV.UK, IMDb, NBA, Recreation.gov, and BoardGameGeek`.
Then point your agent at `http://localhost:40000` through `http://localhost:40037` to explore 38 local mirrors of WebVoyager sites: `Allrecipes, Amazon, Apple, ArXiv, BBC News, Booking, GitHub, Google Flights, Google Maps, Google Search, Hugging Face, Wolfram Alpha, Cambridge Dictionary, Coursera, ESPN, Merriam-Webster, IKEA, Phys.org, Target, TED, Ohio State University, Rotten Tomatoes, Compass, Walmart Careers, FedEx, WebMD Doctor, Healthline, Kaggle, NVIDIA, UC Berkeley, B&H Photo, AccuWeather, GOV.UK, IMDb, NBA, Recreation.gov, BoardGameGeek, and CarMax`.

For sub-second reset between rollouts, expose the control plane and call `/reset/<site>`:

Expand All @@ -66,10 +66,11 @@ git clone https://github.com/aiming-lab/WebHarbor && cd WebHarbor

### Site registry

This checkout registers **37 sites**. NVIDIA remains at index 28, UC Berkeley
This checkout registers **38 sites**. NVIDIA remains at index 28, UC Berkeley
remains at index 29, B&H Photo remains at index 30, AccuWeather remains at index
31, GOV.UK remains at index 32, IMDb remains at index 33 and NBA remains at
index 34. Recreation.gov remains at index 35; BoardGameGeek is appended at index 36. Build the image from
index 34. Recreation.gov remains at index 35; BoardGameGeek remains at index 36,
and CarMax is appended at index 37. Build the image from
this checkout to use this registry; publishing source does not update the
published Docker image automatically.

Expand All @@ -84,17 +85,18 @@ published Docker image automatically.
| NBA | 34 | 40034 | 48034 |
| Recreation.gov | 35 | 40035 | 48035 |
| BoardGameGeek | 36 | 40036 | 48036 |
| CarMax | 37 | 40037 | 48037 |

`websyn_start.sh`, `control_server.py`, the `Dockerfile` `EXPOSE` line and every
site's `tasks.jsonl` `web` URL agree on 37 sites and `40000-40036`;
site's `tasks.jsonl` `web` URL agree on 38 sites and `40000-40037`;
`scripts/check_site_registry.py` (run by `scripts/check_assets.sh`) fails when they
drift.

After preparing the candidate assets and building `webharbor:dev`, the local
review deployment uses:

```bash
docker run -p 127.0.0.1:48080:8101 -p 127.0.0.1:48000-48036:40000-40036 webharbor:dev
docker run -p 127.0.0.1:48080:8101 -p 127.0.0.1:48000-48037:40000-40037 webharbor:dev
```

NVIDIA inherits the site contribution from @KaKituken
Expand Down
2 changes: 1 addition & 1 deletion control_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
'fedex', 'webmd_doctor', 'healthline', 'kaggle',
'nvidia', 'berkeley', 'bh_photo', 'accuweather', 'gov_uk',
'imdb',
'nba', 'recreation_gov', 'boardgamegeek',
'nba', 'recreation_gov', 'boardgamegeek', 'carmax',
]
BASE_PORT = 40000
WEBSYN_DIR = '/opt/WebSyn'
Expand Down
43 changes: 43 additions & 0 deletions scripts/verify_carmax.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Build and verify all registered sites; reset CarMax byte-identically.
# Uses an owned container only. Override ports/tag with CARMAX_VERIFY_*.
set -euo pipefail
cd "$(dirname "$0")/.."

verify_container="wh-carmax-verify-$$"
verify_image="${CARMAX_VERIFY_IMAGE:-webharbor:carmax-check}"
verify_control="${CARMAX_VERIFY_CONTROL_PORT:-8201}"
verify_low="${CARMAX_VERIFY_BASE_PORT:-41000}"
verify_count=$(python3 -c 'import ast; m=ast.parse(open("control_server.py").read()); print(len(next(ast.literal_eval(n.value) for n in m.body if isinstance(n,ast.Assign) and any(isinstance(t,ast.Name) and t.id=="SITES" for t in n.targets))))')
verify_high=$((verify_low + verify_count - 1))
verify_internal_high=$((40000 + verify_count - 1))
verify_started=0
cleanup() {
if (( verify_started )); then docker stop "$verify_container" >/dev/null; fi
}
trap cleanup EXIT

./scripts/build.sh "$verify_image"
docker run -d --rm --name "$verify_container" \
-p "127.0.0.1:$verify_control:8101" \
-p "127.0.0.1:$verify_low-$verify_high:40000-$verify_internal_high" \
"$verify_image"
verify_started=1
verify_ready=0
for ((attempt=0; attempt<120; attempt++)); do
if curl -fsS --max-time 5 "http://localhost:$verify_control/health" >/dev/null 2>&1; then
verify_ready=1
break
fi
sleep 1
done
if (( ! verify_ready )); then docker logs "$verify_container"; exit 1; fi
for ((port=verify_low; port<=verify_high; port++)); do
code=$(curl -sS --max-time 15 -o /dev/null -w '%{http_code}' "http://localhost:$port/")
[[ "$code" == 200 ]] || { echo "Homepage $port returned $code"; exit 1; }
done
curl -fsS --max-time 30 -X POST "http://localhost:$verify_control/reset/carmax"
verify_hashes=$(docker exec "$verify_container" md5sum \
/opt/WebSyn/carmax/instance/carmax.db /opt/WebSyn/carmax/instance_seed/carmax.db)
[[ $(awk '{print $1}' <<< "$verify_hashes" | sort -u | wc -l) -eq 1 ]]
echo "PASS: $verify_count homepages, healthy control plane, byte-identical CarMax reset."
Loading