RAVIO RAVIO/README.md
The amp harness's real work, as a drivable sky road. The signs you pass are the changes that actually happened, altitude is the evidence rung, the gauges are amp's own per-lane…

RAVIO

The amp harness's real work, as a drivable sky road. The signs you pass are the changes that actually happened, altitude is the evidence rung, the gauges are amp's own per-lane ratings, and the dashboard is a page you can scroll, repaint, post from and read your audience on.

This file is how to run it. Two others matter:

  • docs/spec/README.md — the design of record. Read it before changing

anything; it is where the reasons live.

  • PACKET_README.md — the 2026-08-04 status packet: what is measured, what is

claimed, what is blocked, and the four decisions that are not mine to make.

Running it

Nothing below needs anything installed that this machine does not already have. Every service binds 127.0.0.1 only, and the bridge reads .amp/amp.db read-only — it never writes to amp.

1. Restore the vendored engine (once, and after any fresh clone)

cd RAVIO
python3 tools/vendor.py

world/assets/three.module.js, backdrop.jpg and both logo SVGs are gitignored on purpose: they are unmodified copies of files in ~/Projects/Travelmonstr/, and world/assets/PROVENANCE.json records the source path and sha256 of each so drift is detectable. One source of truth.

2. The amp console — optional, but it is what the road is ABOUT

cd ..  &&  ./amp serve      # serves the console on 127.0.0.1:8787

Without it the derived ratings are simply absent and the road keeps driving — that is deliberate, and the cockpit says which nothing it is showing rather than rendering an encouraging zero. With it, three things light up that otherwise cannot: the seven-tube rack, the CONTROLS rows in the deck, and the the code editor · amp console screen (which is proxied same-origin through the bridge, because the console refuses cross-site /api/ calls).

The bridge finds it on 8787. server.py bumps to the next free port if that one is taken, so check what it printed rather than assuming.

3. The bridge — this is the server

python3 bridge.py --port 8890        # then open http://127.0.0.1:8890/
python3 bridge.py --once             # or: one pass, printing world.json + a probe

Use `127.0.0.1`, not `localhost`. They are separate localStorage origins, so opening the other one silently loses your gear, tabs, theme and lane.

Its routes, so a change here is a change you can see:

/world.jsonthe journal the road is built from
/controls.jsonwhat amp publishes for a lane — the deck's CONTROLS
/inspect.jsonthe evidence behind one rating
/narration.json, /speak, /listen§11's voice
/wall.json, /wall, /wall/ingestthe deck's WALL
/social.json, /social, /social/fetch, /social/heartbeat, /social/statusthe deck's ACCOUNTS
/console, /api/…, /ext/<host>the same-origin proxies
/soakthe §6.5 instrument

4. Voice — a second terminal; the road drives fine without it

bash tools/voicebox.sh              # 127.0.0.1:17493, models load on first use
curl -s 127.0.0.1:17493/healthz     # says which engine, and whether it fell back

Kokoro-82M speaks and whisper-base.en hears, out of ~/.cache/ravio-voice-venv (created --system-site-packages so it sees the system torch). mms-tts-eng stays as a fallback so a missing model degrades to a worse voice rather than to silence — and /healthz reports which, so the downgrade cannot be silent. A 404 from /listen means "no ear", not an error.

5. The social fetcher — only if you want the wall to fill

python3 tools/social_fetch.py --login https://x.com/login   # sign in, by hand
python3 tools/social_fetch.py --once                        # one pass
python3 tools/social_fetch.py                               # stay up

Configure the pages, intervals and selectors in the cockpit: lean in (V), scroll the deck up, ACCOUNTS. Nothing here handles a password or a token — --login opens a page in the fetcher's own Chrome profile (~/.cache/ravio-social-profile) and stops, and the session lives in that profile.

Signing in: read this before trying to reuse your own Chrome.

The obvious plan is to point the fetcher at the browser you are already logged into. Chrome does not allow it. Since 136 it refuses to expose a debugging port on the default profile, and says so:

$ google-chrome-stable --remote-debugging-port=9223
DevTools remote debugging requires a non-default data directory.
Specify this using --user-data-dir.

(And if an instance is already running you do not even get that far — it prints Opening in existing browser session., hands the URL over and drops the flag silently.) The restriction is deliberate: an open debugging port lets anything local read that profile's cookies. So a port can only ever be opened on a profile that is not the one holding your logins, which is precisely the thing that made the idea attractive.

So: sign in once, in the fetcher's own profile. Press SIGN IN on a card and the fetcher opens that service's login page in the browser it drives (~/.cache/ravio-social-profile) — you type into a real Chrome window, the session stays in that profile, and RAVIO never sees a credential. Then press CONNECT. Three sign-ins, once. Equivalent from a terminal:

python3 tools/social_fetch.py --login x        # or youtube, twitch

YOUR CHROME · PORT is still there and still works — it is for a Chrome you start yourself on a non-default profile, and it gets you a browser you can watch the fetcher work in.

The order matters, because a profile takes exactly one Chrome. Whichever browser is on ~/.cache/ravio-social-profile holds an exclusive SingletonLock on it, so starting a second one there fails with

Failed to create .../SingletonLock: File exists (17)
Failed to create a ProcessSingleton for your profile directory. Aborting now
to avoid profile corruption.

So: set the port in ACCOUNTS first. The fetcher notices within a tick, stops launching its own browser (every call goes through --cdp from then on) and closes the one it had — it logs closed my own browser so you can start one on that profile yourself. Then:

python3 tools/social_fetch.py --browser

Use that, not `google-chrome-stable`. agent-browser ships its own Chrome and writes the profile with it, so opening that profile with the system Chrome is a version downgrade: measured, agent-browser's 147 created the profile, google-chrome-stable (142) opened it, reset the quota database and died with SIGTRAPafter the port was already listening, so it looked like it had worked. --browser picks the same build agent-browser uses, clears a stale SingletonLock if a previous Chrome died without releasing one, waits for the port, and says so:

[social] cleared a stale lock left by pid 2448741 (that process is gone)
[social] launching .../chrome-147.0.7727.57/chrome
[social] up on port 9223

Each read still opens its own tab and closes it — verified against a real CDP Chrome: a fetch left the pre-existing tab exactly where it was.

If you want your existing logins in that profile rather than signing in again, the only route is copying them out of ~/.config/google-chrome (Cookies, Login Data and Local State, with Chrome closed) — a second copy of your session tokens on disk, a snapshot that drifts, and Google in particular may challenge the session as a new device. Not built, and not recommended over three sign-ins.

CONNECT, and why it is not OAuth. Each card has SIGN IN and CONNECT. CONNECT opens that service's own page in the fetcher's profile, reads your handle off it, and points the watch URL at you (https://x.com/{handle}/with_replies, and so on). There is no developer app, no client secret and no token, because none of them would buy anything — the fetcher drives a signed-in browser, so it never uses a token, and OAuth would exist purely to learn one string that this browser can be asked for directly. Also runnable as tools/social_fetch.py --connect x.

The handle is read one of two ways, both editable: an element on a page only you can see (who.sel), or the address the page landed on — the second is sturdier, because studio.youtube.com redirecting to /channel/<you> is a fact about the account rather than a class name somebody can rename. When it fails it says which failure: signed out or the selector moved is a different sentence from could not find a handle in it.

agent-browser runs one browser per machine. Measured, twice: --profile is silently ignored whenever a session is already up, and every caller shares that window — so while the fetcher runs, any other agent-browser command lands in the same browser, and a fetch pass will navigate a window you are looking at. The fetcher refuses to read a browser that is on a different profile (what it found there would be somebody else's account), but it cannot tell that you are sharing its own. For real isolation, start a Chrome yourself and point at it:

google-chrome-stable --remote-debugging-port=9223 --user-data-dir=$HOME/.cache/ravio-social-profile
python3 tools/social_fetch.py --cdp 9223

(chrome is not the binary on this machine — google-chrome-stable is. Check with which google-chrome-stable chromium before copying this anywhere else.)

The default selectors for X, YouTube and Twitch are unverified guesses — nobody here has an account to log into. That is why every pass reports matched (what the item selector found) separately from added (what was new): a page that rendered 40 comments and added 0 is a quiet night, a selector that matched 0 is a broken fetch, and one number cannot say which. The ACCOUNTS tab shows both, and the selectors are editable there.

6. Is any of it running?

ss -ltnp | grep -E ':8787|:8890|:17493'     # console · bridge · voice
curl -s 127.0.0.1:8890/social.json | python3 -m json.tool | grep daemon_at
tail -f RAVIO/.ravio/bridge.log RAVIO/.ravio/fetch.log

daemon_at is the only honest answer to "is the fetcher up". Not the newest check-in: a loop watching three sources on ten-minute intervals reports nothing for most of its life, and a --once pass that exited two minutes ago still has a fresh one — wrong in both directions. The ACCOUNTS tab reads the same field, which is why FETCH NOW can say "asked, but no fetcher has checked in recently" instead of spinning.

To stop things: pkill -f social_fetch.py, pkill -f 'bridge.py --port 8890'. Nothing here writes to amp, so nothing is lost by killing it. The bridge's own files (.ravio/) survive a restart; the social config does not survive a `rm .ravio/social.json`, and the bridge holds it in memory — so deleting the file under a running bridge does nothing until you restart it.

7. Measurement, once it is running

python3 tools/soak_report.py --hours 24   # §6.5; refuses to extrapolate a short span
python3 tools/dial_check.py               # the change-rate measurement that set S=300
bash    tools/legibility.sh               # §6.3 static: needs chrome + ffmpeg
bash    tools/legibility_motion.sh        # §6.3 approach: the one that actually bites

The soak needs a displayed window. Backgrounded, the page runs at 1 fps and the report will tell you so rather than average it in.

Driving it

W A S D / drag the skyfly
Vlean into the cockpit — opens the deck
wheelscrub the road while flying; scroll the deck while leaned in
G / Escwalk the P/R/C/D gate · Esc in D closes the innermost open thing
PgUp/PgDn, , .scrub the journal · step to the next thing that actually happened
17pick a tube · O the tape · M mute · B status · T reply · K camera
[ ] / { }walk the lane roster / the districts

The deck is the dashboard as a page: lean in for a peek, keep scrolling and the dash rises off it. Four tabs — CONTROLS (what amp publishes for this lane), THEME (the paint on the ship, remembered per browser), THE WALL (what this cockpit posted and what came back), ACCOUNTS (§5 above).

A page you open on the screen keeps its state — the amp console holds all its navigation in memory and never touches the URL, so only keeping the document alive can bring it back where you left it. ◀ CONTROLS on the page's header bar, or Esc, closes it and lands on the deck.

What is deliberately not in the repo

The vendored engine and sky (§1), dist/ (24 MB of PNG evidence), and .ravio/ (the runtime journal, the soak log, the wall and the accounts config). .ravio/social.json in particular is per-machine and holds the pages you watch; it holds no credential, and there is nothing in it worth committing.

Open in the interactive atlas