Status, plan and log for the show machine
Generated from system.json by
tools/system_docs.py; don’t edit by hand. Interactive version: https://gavaiken.github.io/river-bitcube/system/
Every machine, device, service and connection of A River Becoming, for people and agents. Source of truth for docs/system/index.html (interactive) and docs/system/system.md (generated by tools/system_docs.py). Validated by tools/tests/test_system_json.py. Optional display fields: node.short (diagram name) and link.label (text on the arrow).
Updated 2026-10-18T18:10:00-07:00.
Rules
now, 18 Oct 2026)Prototype at Agape. bitcube runs the local rain checkpoint on 18 of 20 ropes and HUB75 quad 1, with no Modal input yet. The gallery cam is on Agape WiFi, and bitcube shares it on Will’s tailnet (10.0.7.76/32). The GL.iNet router and the river cam left for the river with Colin on the evening of 18 Oct. Modal’s agent ticks every 5 minutes, but nothing on bitcube reads it yet.
flowchart LR
subgraph p_river["The River"]
cam_river["River cam<br/>192.168.8.217"]
glinet["GL.iNet router<br/>192.168.8.1"]
starlink["Starlink"]
river_power["Solar and battery"]
end
subgraph p_agape["Agape (prototype)"]
bitcube["bitcube<br/>10.0.7.79"]
m14["M14 monitor"]
colorlight_sender["Colorlight sender"]
receive_card["5A-75B receive card"]
hub75_quad1["HUB75 quad 1"]
hub75_quad2["HUB75 quad 2"]
hub75_more["Other HUB75 panels"]
psac10_1["PSAC10 box 1<br/>2.0.0.10"]
ropes_psac10_1["Ropes 1-10 (OUT1-OUT10)"]
psac10_2["PSAC10 box 2<br/>2.0.0.11"]
ropes_psac10_2["Ropes 11-20 (OUT1-OUT10)"]
rack_controller["Rack LED controller"]
cam_gallery["Gallery cam<br/>10.0.7.76"]
eero["Agape eero<br/>10.0.7.1"]
gavin_mini["Gavin's Mac mini<br/>10.0.7.203"]
end
subgraph p_modal["Modal cloud"]
modal_app["river-agent app"]
ep_data["GET /data/{file}"]
ep_ingest["POST /ingest/…"]
ep_canvas["GET /canvas.html"]
ep_page["GET / (page)"]
modal_volume["river-agent-data"]
modal_vllm["DeepSeek-V4-Flash"]
modal_perception["Perception"]
modal_clef["Clef-Omni"]
end
subgraph p_internet["Internet and Tailscale"]
gavin_air["Gavin's Air<br/>10.0.7.28"]
andi_pc["estrogen (Andi)"]
smelterplume["smelterplume"]
dave_laptop["Dave's computer"]
public_internet["Public internet"]
open_meteo["Open-Meteo"]
tailnet_will["Will's tailnet"]
tailnet_gavin["Gavin's tailnet"]
end
subgraph p_github["GitHub"]
repo_river_bitcube["river-bitcube"]
repo_river_agent["river-agent"]
gh_pages["Status site (Pages)"]
gh_snapshot["snapshot branch"]
end
bitcube -->|"Art-Net U0–39"| psac10_1
bitcube -->|"Art-Net U40–79"| psac10_2
rack_controller -->|"NET1"| psac10_1
rack_controller -->|"AoShow controller link (Ethernet)"| psac10_2
psac10_1 -->|"XLR 24 V + SPI"| ropes_psac10_1
psac10_2 -->|"XLR 24 V + SPI"| ropes_psac10_2
bitcube -->|"HDMI 1920×1080"| colorlight_sender
colorlight_sender -->|"Colorlight"| receive_card
receive_card -->|"HUB75"| hub75_quad1
receive_card -.->|"HUB75 ribbon"| hub75_quad2
receive_card -.->|"HUB75 ribbon"| hub75_more
bitcube -->|"USB-C"| m14
bitcube <-->|"Wi-Fi"| eero
eero <-->|"ISP uplink"| public_internet
glinet -.-|"Ethernet to the Starlink router"| starlink
starlink -.-|"Starlink"| public_internet
cam_river <-->|"LAN"| glinet
cam_gallery <-->|"Wi-Fi"| eero
bitcube -->|"RTSP"| cam_gallery
bitcube -->|"HTTPS 443 JSON API /cgi-bin/api.cgi"| cam_gallery
bitcube -.->|"RTSP via Tailscale"| cam_river
bitcube -.->|"HTTPS 443 JSON API via Tailscale"| cam_river
bitcube <-->|"WireGuard (UDP 41641)"| tailnet_will
glinet -.-|"routes 192.168.8.0/24"| tailnet_will
andi_pc <-->|"WireGuard"| tailnet_will
smelterplume <-->|"WireGuard"| tailnet_will
dave_laptop -.-|"WireGuard"| tailnet_will
gavin_mini <-->|"WireGuard"| tailnet_gavin
gavin_air <-->|"WireGuard"| tailnet_gavin
tailnet_will -->|"node share"| tailnet_gavin
gavin_mini -->|"SSH"| bitcube
gavin_air -->|"VNC"| bitcube
gavin_air -->|"RDP"| bitcube
gavin_air -->|"SMB"| bitcube
andi_pc -->|"RDP 3389 / VNC 5900"| bitcube
dave_laptop -.->|"SSH"| bitcube
bitcube -->|"git over SSH (github.com:22)"| repo_river_bitcube
bitcube -->|"jpg / 15 min"| gh_snapshot
gavin_mini <-->|"git (GitHub"| repo_river_bitcube
gavin_mini <-->|"git (GitHub"| repo_river_agent
repo_river_bitcube -->|"Pages build"| gh_pages
bitcube -->|"HTTPS"| gh_pages
repo_river_agent -->|"deploy on push"| modal_app
bitcube -.->|"GET /data every 3 s"| ep_data
bitcube -.->|"canvas.html"| ep_canvas
bitcube -.->|"POST clips"| ep_ingest
ep_data -->|"Modal Volume mount"| modal_volume
ep_ingest -->|"Modal Volume mount"| modal_volume
modal_app -->|"web function"| ep_canvas
modal_app -->|"web function"| ep_page
modal_app -->|"web function"| ep_data
modal_app -->|"web function"| ep_ingest
modal_app <-->|"Modal Volume mount"| modal_volume
modal_app -->|"LLM calls"| modal_vllm
modal_app -->|"HTTPS"| modal_perception
modal_app -->|"Modal Cls.remote() call"| modal_clef
modal_app -->|"HTTPS GET"| open_meteo
tailnet_will -->|"route 10.0.7.76/32"| cam_gallery
show, install from Wed 22 Oct 2026, show to 27 Oct)Planned and partly unsettled. bitcube and all lighting move to Gray Area: lighting NIC on a switch to the rack and PSAC10s, internet from the gallery network on the other NIC or Wi-Fi. The gallery cam joins the gallery Wi-Fi and bitcube advertises its address on Will’s tailnet, as at Agape. bitcube mirrors Modal’s scene (sync.py, :8770) and pushes gallery clips to /ingest. The river cam is at the river on Starlink.
Open questions: Gallery network: the dual-NIC plan. (Reaching the gallery cam over Tailscale: bitcube advertises its address as a /32, as at Agape since 18 Oct.); Give the gallery cam a fixed address? At Gray Area, gallery Wi-Fi or back on the GL.iNet?; At the river: is the GL.iNet on Starlink, and what runs the capture agent that pushes river clips to /ingest (bitcube over Tailscale via 192.168.8.0/24, or a device there)?; Which location/channel names and clip cadence should bitcube use for camera ingest? (Code: locations gallery/river, channel video, 10 s clips.); Can bitcube get an INGEST_TOKEN? (SITE_TOKEN: answered, it is not set.); OK to run gallery/sync.py on bitcube (port 8770)?; What should the gallery show if bitcube goes down? (Today: the rack controller’s SD-card colour cycle.); Does the show need a TouchDesigner Commercial licence? (bitcube has Non-Commercial.)
flowchart LR
subgraph p_river["The River"]
cam_river["River cam<br/>192.168.8.217"]
glinet["GL.iNet router<br/>192.168.8.1"]
starlink["Starlink"]
river_power["Solar and battery"]
end
subgraph p_agape["Agape (prototype)"]
eero["Agape eero<br/>10.0.7.1"]
gavin_mini["Gavin's Mac mini<br/>10.0.7.203"]
end
subgraph p_grayarea["Gray Area (show)"]
bitcube["bitcube<br/>2.0.0.8"]
m14["M14 monitor"]
colorlight_sender["Colorlight sender"]
receive_card["5A-75B receive card"]
hub75_quad1["HUB75 quad 1"]
hub75_quad2["HUB75 quad 2"]
hub75_more["Other HUB75 panels"]
psac10_1["PSAC10 box 1<br/>2.0.0.10"]
ropes_psac10_1["Ropes 1-10 (OUT1-OUT10)"]
psac10_2["PSAC10 box 2<br/>2.0.0.11"]
ropes_psac10_2["Ropes 11-20 (OUT1-OUT10)"]
rack_controller["Rack LED controller"]
cam_gallery["Gallery cam<br/>unknown"]
gallery_net["Gallery network"]
gallery_switch["Lighting switch"]
end
subgraph p_modal["Modal cloud"]
modal_app["river-agent app"]
ep_data["GET /data/{file}"]
ep_ingest["POST /ingest/…"]
ep_canvas["GET /canvas.html"]
ep_page["GET / (page)"]
modal_volume["river-agent-data"]
modal_vllm["DeepSeek-V4-Flash"]
modal_perception["Perception"]
modal_clef["Clef-Omni"]
end
subgraph p_internet["Internet and Tailscale"]
gavin_air["Gavin's Air<br/>10.0.7.28"]
andi_pc["estrogen (Andi)"]
smelterplume["smelterplume"]
dave_laptop["Dave's computer"]
public_internet["Public internet"]
open_meteo["Open-Meteo"]
tailnet_will["Will's tailnet"]
tailnet_gavin["Gavin's tailnet"]
end
subgraph p_github["GitHub"]
repo_river_bitcube["river-bitcube"]
repo_river_agent["river-agent"]
gh_pages["Status site (Pages)"]
gh_snapshot["snapshot branch"]
end
bitcube -->|"Art-Net U0–39"| psac10_1
bitcube -->|"Art-Net U40–79"| psac10_2
rack_controller -->|"NET1"| psac10_1
rack_controller -->|"AoShow controller link (Ethernet)"| psac10_2
bitcube -.-|"Ethernet (2.0.0.8/8 lighting network)"| gallery_switch
gallery_switch -.-|"Ethernet"| rack_controller
psac10_1 -->|"XLR 24 V + SPI"| ropes_psac10_1
psac10_2 -->|"XLR 24 V + SPI"| ropes_psac10_2
bitcube -->|"HDMI 1920×1080"| colorlight_sender
colorlight_sender -->|"Colorlight"| receive_card
receive_card -->|"HUB75"| hub75_quad1
receive_card -.->|"HUB75 ribbon"| hub75_quad2
receive_card -.->|"HUB75 ribbon"| hub75_more
bitcube -->|"USB-C"| m14
bitcube -.-|"Wi-Fi or Ethernet 2 (gallery network)"| gallery_net
eero <-->|"ISP uplink"| public_internet
gallery_net -.-|"Gallery ISP uplink"| public_internet
glinet -.-|"Ethernet to the Starlink router"| starlink
starlink -.-|"Starlink"| public_internet
cam_river <-->|"LAN"| glinet
cam_gallery -.-|"Wi-Fi (gallery network)"| gallery_net
bitcube -->|"RTSP"| cam_gallery
bitcube -->|"HTTPS 443 JSON API /cgi-bin/api.cgi"| cam_gallery
bitcube -.->|"RTSP via Tailscale"| cam_river
bitcube -.->|"HTTPS 443 JSON API via Tailscale"| cam_river
bitcube <-->|"WireGuard (UDP 41641)"| tailnet_will
glinet -.-|"routes 192.168.8.0/24"| tailnet_will
andi_pc <-->|"WireGuard"| tailnet_will
smelterplume <-->|"WireGuard"| tailnet_will
dave_laptop -.-|"WireGuard"| tailnet_will
gavin_mini <-->|"WireGuard"| tailnet_gavin
gavin_air <-->|"WireGuard"| tailnet_gavin
tailnet_will -->|"node share"| tailnet_gavin
gavin_mini -->|"SSH"| bitcube
gavin_air -->|"VNC"| bitcube
gavin_air -->|"RDP"| bitcube
gavin_air -->|"SMB"| bitcube
andi_pc -->|"RDP 3389 / VNC 5900"| bitcube
dave_laptop -.->|"SSH"| bitcube
bitcube -->|"git over SSH (github.com:22)"| repo_river_bitcube
bitcube -->|"jpg / 15 min"| gh_snapshot
gavin_mini <-->|"git (GitHub"| repo_river_bitcube
gavin_mini <-->|"git (GitHub"| repo_river_agent
repo_river_bitcube -->|"Pages build"| gh_pages
bitcube -->|"HTTPS"| gh_pages
repo_river_agent -->|"deploy on push"| modal_app
bitcube -.->|"GET /data every 3 s"| ep_data
bitcube -.->|"canvas.html"| ep_canvas
bitcube -.->|"POST clips"| ep_ingest
cam_river -.->|"POST clips"| ep_ingest
ep_data -->|"Modal Volume mount"| modal_volume
ep_ingest -->|"Modal Volume mount"| modal_volume
modal_app -->|"web function"| ep_canvas
modal_app -->|"web function"| ep_page
modal_app -->|"web function"| ep_data
modal_app -->|"web function"| ep_ingest
modal_app <-->|"Modal Volume mount"| modal_volume
modal_app -->|"LLM calls"| modal_vllm
modal_app -->|"HTTPS"| modal_perception
modal_app -->|"Modal Cls.remote() call"| modal_clef
modal_app -->|"HTTPS GET"| open_meteo
tailnet_will -.->|"route via bitcube"| cam_gallery
Solid arrows are working; dotted arrows are planned, stopped, blocked or unknown. An arrow points away from the side that starts the connection.
| Place | Scenarios | Status | Summary |
|---|---|---|---|
The River (river) |
now, show | partial | The real river the agent watches. The GL.iNet router and the Reolink E1 Outdoor Pro went there with Colin on 18 Oct; Starlink and solar/battery power are expected there (unverified). Area: near Dobbins, California. |
Agape (prototype) (agape) |
now, show | working | Gavin’s house in the Mission, SF, where everything is prototyped until the Gray Area install. Gavin’s Mac mini stays here. Network: lan: 10.0.7.0/24; gateway: 10.0.7.1 (eero); wifi: Agape WiFi. |
Gray Area (show) (grayarea) |
show | planned | The gallery. Install Wed 22 to Mon 27 Oct 2026. Network plan not settled (open questions to Will). Address: Gray Area, 2665 Mission St, San Francisco. |
Modal cloud (modal) |
now, show | working | Dave’s agent: one Modal app (tick, watch, web) plus model apps, in workspace to-be-a-river. |
Internet and Tailscale (internet) |
now, show | working | The public internet, the two tailnets, and team devices that aren’t tied to a site. |
GitHub (github) |
now, show | working | The two repos, the public status site (GitHub Pages) and the camera snapshot branch. |
cam-river)camera · unknown · confirmed · owner Will Gardiner · now: The River · show: The River
Reolink E1 Outdoor Pro behind the GL.iNet router. Left Agape for the river with Colin on the evening of 18 Oct (it used to sit on the rack controller here).
glinet)router · unknown · confirmed · owner Will Gardiner · now: The River · show: The River
GL.iNet GL-AXT1800 (Slate AX) travel router running Tailscale: puts the river cam’s subnet 192.168.8.0/24 on Will’s tailnet. Left Agape for the river with Colin and the river cam on the evening of 18 Oct; offline at 17:50.
tailscale status and tailscale status --json on bitcube (Will’s tailnet view); docs/a-river-becoming-hardware.md (verified hardware); Session A notes, 18 Oct 17:50 and 17:55 (docs/sessions/A.md): GL.iNet to the river, route changes, gallery cam via bitcube, HUB75 gainstarlink)router · planned · unverified · owner Will Gardiner · now: The River · show: The River
Satellite uplink for the river kit. Outbound only (carrier-grade NAT), so devices there must push out, or be reached over Tailscale.
river-power)power · planned · unverified · owner Will Gardiner · now: The River · show: The River
Off-grid power for the river kit (camera, router, Starlink).
bitcube)computer · working · confirmed · owner Will Gardiner · now: Agape (prototype) · show: Gray Area (show)
The show machine: a Windows mini PC that runs TouchDesigner, drives the ropes (Art-Net) and the HUB75 (HDMI), reads the cameras, and will mirror Modal’s scene.
ssh bitcube.tscon <id> /dest:console.tools\td.ps1 uses it.ssh bitcube: OS, NICs, IPs, routes, monitors, listening ports, services, scheduled tasks, SMB shares, firewall rules, ping of cameras and PSAC10s; tailscale status and tailscale status --json on bitcube (Will’s tailnet view); docs/a-river-becoming-log.md (session history); CLAUDE.md (facts and gotchas for bitcube); Session A notes, 18 Oct 17:50 and 17:55 (docs/sessions/A.md): GL.iNet to the river, route changes, gallery cam via bitcube, HUB75 gainm14)display · working · confirmed · owner Gavin Aiken · now: Agape (prototype) · show: Gray Area (show)
Lenovo M14 portable monitor on USB-C: bitcube’s primary display, so the desktop and taskbar stay off the panels.
ssh bitcube: OS, NICs, IPs, routes, monitors, listening ports, services, scheduled tasks, SMB shares, firewall rules, ping of cameras and PSAC10s; CLAUDE.md (facts and gotchas for bitcube)colorlight-sender)led-controller · working · confirmed · owner Andi Doud · now: Agape (prototype) · show: Gray Area (show)
LED video sender. Looks like a 1920x1080 HDMI display to bitcube and samples its top-left region for the receive card.
ssh bitcube: OS, NICs, IPs, routes, monitors, listening ports, services, scheduled tasks, SMB shares, firewall rules, ping of cameras and PSAC10sreceive-card)led-controller · working · confirmed · owner Andi Doud · now: Agape (prototype) · show: Gray Area (show)
Receive card that drives the HUB75 panels from the sender’s stream.
hub75-quad1)display · working · confirmed · owner Andi Doud · now: Agape (prototype) · show: Gray Area (show)
Four 128x64 panels as a 2x2 quad (256x128). Verified by camera: red TL with the quad-1 marker, green TR, blue BL, yellow BR.
hub75-quad2)display · blocked · awaiting:Andi · owner Andi Doud · now: Agape (prototype) · show: Gray Area (show)
Second quad, reserved at HDMI x 256-511. Not connected yet: waiting on Andi’s receive-card layout. Blanked in TD.
hub75-more)display · planned · unverified · owner Andi Doud · now: Agape (prototype) · show: Gray Area (show)
About 30 HUB75 panels exist in total (the ‘pools’); how many go into the show beyond quads 1 and 2 is not settled.
psac10-1)led-controller · working · confirmed · owner Colin · now: Agape (prototype) · show: Gray Area (show)
AoShow AS_PSAC10 Art-Net node and 1,750 W PSU. Ropes 1-10 on OUT1-OUT10.
ssh bitcube: OS, NICs, IPs, routes, monitors, listening ports, services, scheduled tasks, SMB shares, firewall rules, ping of cameras and PSAC10s; docs/a-river-becoming-log.md (session history)psac10-2)led-controller · working · confirmed · owner Colin · now: Agape (prototype) · show: Gray Area (show)
Second AS_PSAC10. Ropes 11-20 on OUT1-OUT10.
ssh bitcube: OS, NICs, IPs, routes, monitors, listening ports, services, scheduled tasks, SMB shares, firewall rules, ping of cameras and PSAC10srack-controller)led-controller · standby · confirmed · owner Colin · now: Agape (prototype) · show: Gray Area (show)
AoShow 1U master player (AS_MSDMX). Plays its SD-card program (program 2 lights the ropes) only when no Art-Net arrives.
cam-gallery)camera · working · confirmed · owner Will Gardiner · now: Agape (prototype) · show: Gray Area (show)
Reolink E1 Pro (indoor, pan/tilt, mic). At Agape on Agape WiFi, aimed at all coil piles, the HUB75 and the PSAC10s. Used for calibration, snapshots and (planned) the agent’s gallery eye.
eero)router · working · confirmed · owner Gavin Aiken · now: Agape (prototype) · show: Agape (prototype)
The house router: gateway, DHCP and DNS for Agape WiFi (10.0.7.0/24).
ssh bitcube: OS, NICs, IPs, routes, monitors, listening ports, services, scheduled tasks, SMB shares, firewall rules, ping of cameras and PSAC10sgavin-mini)computer · working · confirmed · owner Gavin Aiken · now: Agape (prototype) · show: Agape (prototype)
Always-on Mac mini at Agape: session A (the coordinator) and docs sessions like this one. Pulls the repo, can SSH into bitcube.
ssh bitcube -> user@100.73.68.60; auth: ~/.ssh/id_ed25519tailscale status on Gavin’s Mac mini (Gavin’s tailnet view)Each rope: AoShow 360 LED Flex Neon, 10 m, 400 px RGB (UCS1903, 24 V), 3 universes used of 4 reserved. Rope n is box ⌈n/10⌉, OUT ((n−1) mod 10)+1, universes 4(n−1) to 4(n−1)+2 (Will’s config.local.json).
| Rope | Box / OUT | Universes | Pile at Agape | Visible px (camera) | Status |
|---|---|---|---|---|---|
Rope 1 (rope-01) |
box 1 OUT1 | 0-2 | right | 99 | working |
Rope 2 (rope-02) |
box 1 OUT2 | 4-6 | centre | 118 | working |
Rope 3 (rope-03) |
box 1 OUT3 | 8-10 | centre | 141 | working |
Rope 4 (rope-04) |
box 1 OUT4 | 12-14 | right | 53 | working |
Rope 5 (rope-05) |
box 1 OUT5 | 16-18 | centre | 201 | working |
Rope 6 (rope-06) |
box 1 OUT6 | 20-22 | centre | 142 | working |
Rope 7 (rope-07) |
box 1 OUT7 | 24-26 | centre | 77 | working |
Rope 8 (rope-08) |
box 1 OUT8 | 28-30 | right | 117 | working |
Rope 9 (rope-09) |
box 1 OUT9 | 32-34 | not seen (dark) | none | broken: Never lights (box 1 OUT9); swap test deferred, carrying on with 18 ropes. |
Rope 10 (rope-10) |
box 1 OUT10 | 36-38 | right | 207 | working |
Rope 11 (rope-11) |
box 2 OUT1 | 40-42 | left-centre | 68 | working |
Rope 12 (rope-12) |
box 2 OUT2 | 44-46 | far left | 184 | working |
Rope 13 (rope-13) |
box 2 OUT3 | 48-50 | left-centre | 106 | working |
Rope 14 (rope-14) |
box 2 OUT4 | 52-54 | left-centre | 138 | working |
Rope 15 (rope-15) |
box 2 OUT5 | 56-58 | far left | 166 | working |
Rope 16 (rope-16) |
box 2 OUT6 | 60-62 | centre | 79 | working |
Rope 17 (rope-17) |
box 2 OUT7 | 64-66 | not seen (dark) | none | broken: Never lights (box 2 OUT7); swap test deferred, carrying on with 18 ropes. |
Rope 18 (rope-18) |
box 2 OUT8 | 68-70 | left-centre | 94 | working |
Rope 19 (rope-19) |
box 2 OUT9 | 72-74 | far left | 47 | working |
Rope 20 (rope-20) |
box 2 OUT10 | 76-78 | far left | 66 | working |
gallery-net)network · planned · unverified · owner Lilian (Lily) · now: absent · show: Gray Area (show)
Gray Area’s own Wi-Fi/internet. Planned: bitcube’s internet uplink and the gallery cam’s network.
gallery-switch)network · planned · unverified · owner Colin · now: absent · show: Gray Area (show)
A dumb Ethernet switch on bitcube’s lighting NIC (2.0.0.8) for the rack controller and both PSAC10s.
modal-app)cloud-app · working · confirmed · owner Dave · now: Modal cloud · show: Modal cloud
Dave’s Modal app: a cron tick (one agent step every 5 min), a watch function (one per camera clip), and the web function serving the page, /data and /ingest.
ep-data)endpoint · working · confirmed · owner Dave · now: Modal cloud · show: Modal cloud
The live files: status.json, state.json, pad.json, journal_recent.json, reflex.json (+ scenes.jsonl, journal.jsonl). bitcube’s mirror will pull these every 3 s.
ep-ingest)endpoint · working · confirmed · owner Dave · now: Modal cloud · show: Modal cloud
Where sensors push media: Authorization: Bearer
ep-canvas)endpoint · working · confirmed · owner Dave · now: Modal cloud · show: Modal cloud
The sculpture alone, full window, for TouchDesigner’s Web Render TOP. ?w=&h= sets the design area (default 480x270).
ep-page)endpoint · working · confirmed · owner Dave · now: Modal cloud · show: Modal cloud
The installation’s page: the sculpture, a status strip and the agent’s journal. Kiosk mode ?kiosk=1. /healthz answers {“ok”: true}.
modal-volume)storage · working · confirmed · owner Dave · now: Modal cloud · show: Modal cloud
The agent’s state: journal.jsonl, status.json, state.json, scenes.jsonl, pad.json, journal_recent.json, reflex.json, media/
modal-vllm)cloud-app · working · confirmed · owner Dave · now: Modal cloud · show: Modal cloud
The agent’s LLM: Modal app river-agent-vllm-deepseek-v4-flash, OpenAI-compatible, behind Modal proxy auth.
modal-perception)cloud-app · working · confirmed · owner Dave · now: Modal cloud · show: Modal cloud
Modal app river-agent-perception: turns camera frames into text for the tick. Audio perception is off.
modal-clef)cloud-app · working · confirmed · owner Dave · now: Modal cloud · show: Modal cloud
Modal app river-agent-clef: a decision model that answers the agent’s typed questions about each 10 s clip (fast perception). Asleep until clips arrive.
gavin-air)computer · working · confirmed · owner Gavin Aiken · now: Internet and Tailscale · show: Internet and Tailscale
Gavin’s laptop: VNC, RDP (Windows App) and SMB into bitcube over Tailscale. Holds the 7 Oct Chromatik model and TD patch (probably).
tailscale status on Gavin’s Mac mini (Gavin’s tailnet view); docs/a-river-becoming-log.md (session history)andi-pc)computer · working · confirmed · owner Andi Doud · now: Internet and Tailscale · show: Internet and Tailscale
Andi’s Windows PC on Will’s tailnet: Colorlight configuration and TouchDesigner; RDP/VNC into bitcube.
tailscale status and tailscale status --json on bitcube (Will’s tailnet view)smelterplume)computer · unknown · unverified · owner Will Gardiner · now: Internet and Tailscale · show: Internet and Tailscale
A macOS device on Will’s tailnet, logged in with the project account. Probably Will’s laptop (unverified).
tailscale status and tailscale status --json on bitcube (Will’s tailnet view)dave-laptop)computer · unknown · unverified · owner Dave · now: Internet and Tailscale · show: Internet and Tailscale
Dave can SSH into bitcube with his key (added 18 Oct). No Dave device was visible on Will’s tailnet on 18 Oct.
tailscale status and tailscale status --json on bitcube (Will’s tailnet view)public-internet)network · working · confirmed · owner the team · now: Internet and Tailscale · show: Internet and Tailscale
Everything that leaves a site: Modal, GitHub, Open-Meteo, Tailscale’s coordination servers.
open-meteo)service · working · confirmed · owner the team · now: Internet and Tailscale · show: Internet and Tailscale
Free weather API the tick reads for current conditions near the river (no key).
tailnet-will)tailnet · working · confirmed · owner Will Gardiner · now: Internet and Tailscale · show: Internet and Tailscale
The project tailnet: bitcube, the GL.iNet (camera subnet router), Andi’s PC, smelterplume. Free plan, max 6 users.
tailscale status and tailscale status --json on bitcube (Will’s tailnet view); docs/a-river-becoming-log.md (session history)tailnet-gavin)tailnet · working · confirmed · owner Gavin Aiken · now: Internet and Tailscale · show: Internet and Tailscale
Gavin’s own tailnet: the Mac mini (subnet router for Agape 10.0.7.0/24) and the Air. bitcube is node-shared into it as 100.73.68.60.
tailscale status on Gavin’s Mac mini (Gavin’s tailnet view); docs/a-river-becoming-log.md (session history)repo-river-bitcube)repo · working · confirmed · owner Gavin Aiken · now: GitHub · show: GitHub
This repo (public): docs (source of truth), TouchDesigner files and build scripts, tools. Checked out on bitcube and on Gavin’s mini.
repo-river-agent)repo · working · confirmed · owner Dave · now: GitHub · show: GitHub
Dave’s repo (private): the agent, the page, the Modal app. Merges to main deploy to Modal. gavaiken has push access.
gh-pages)website · working · confirmed · owner Gavin Aiken · now: GitHub · show: GitHub
Public status page and these docs, built from main:/docs.
gh-snapshot)repo · working · confirmed · owner Gavin Aiken · now: GitHub · show: GitHub
Orphan branch holding one gallery.jpg, replaced every 15 minutes (08:00-02:00) by bitcube. The status page shows it.
Every connection. Auth names the secret, never its value. Medium colours on the interactive page: artnet Art-Net on the lighting Ethernet (2.0.0.0/8); ethernet other Ethernet between lighting devices; xlr 4-pin XLR, 24 V + SPI data to a rope; video HDMI, USB-C display, Colorlight and HUB75 video chain; lan a local network (Wi-Fi or Ethernet) at a site; tailscale over a tailnet (WireGuard); https HTTPS over the internet; git git pushes, Pages builds, GitHub Actions; starlink Starlink satellite uplink; internal inside Modal.
| Link | From → to | Medium | Protocol | Direction | Auth | Rate | Status | Scenarios | What |
|---|---|---|---|---|---|---|---|---|---|
artnet-box1 |
bitcube → PSAC10 box 1 | artnet | Art-Net ArtDmx, UDP 6454 | one-way | none (isolated lighting network) | 40 fps target, about 35 sent | working (confirmed) | now, show | TouchDesigner sends ropes 1-10: universes 0-39 from 2.0.0.8 to 2.0.0.10. Payload: 80 universes in total (this box 0-39), 170 RGB px per universe, 4 per rope (3 used, 4th black). Path: bitcube Ethernet (2.0.0.8/8) -> rack/box cabling (exact cable path unverified). |
artnet-box2 |
bitcube → PSAC10 box 2 | artnet | Art-Net ArtDmx, UDP 6454 | one-way | none (isolated lighting network) | 40 fps target, about 35 sent | working (confirmed) | now, show | TouchDesigner sends ropes 11-20: universes 40-79 from 2.0.0.8 to 2.0.0.11. Payload: universes 40-79. |
net-rack-box1 |
Rack LED controller → PSAC10 box 1 | ethernet | AoShow controller link (Ethernet, NET1 to NET1) | one-way | working (confirmed) | now, show | Rack NET1 to box 1 NET1. The rack plays its SD-card program over this only when Art-Net stops. | ||
net-box2-feed |
Rack LED controller → PSAC10 box 2 | ethernet | AoShow controller link (Ethernet) | one-way | working (unverified) | now, show | Box 2 is fed from rack NET2, or chained from box 1 NET2: not checked. | ||
lighting-switch |
bitcube → Lighting switch | ethernet | Ethernet (2.0.0.8/8 lighting network) | two-way | planned (unverified) | show | bitcube’s lighting NIC to a dumb switch that feeds the rack controller and both PSAC10s. | ||
switch-rack |
Lighting switch → Rack LED controller | ethernet | Ethernet | two-way | planned (unverified) | show | Switch to the rack controller (and on to the PSAC10s). | ||
xlr-rope-01 |
PSAC10 box 1 → Rope 1 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 1 OUT1 to rope 1 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 0-2. | ||
xlr-rope-02 |
PSAC10 box 1 → Rope 2 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 1 OUT2 to rope 2 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 4-6. | ||
xlr-rope-03 |
PSAC10 box 1 → Rope 3 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 1 OUT3 to rope 3 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 8-10. | ||
xlr-rope-04 |
PSAC10 box 1 → Rope 4 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 1 OUT4 to rope 4 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 12-14. | ||
xlr-rope-05 |
PSAC10 box 1 → Rope 5 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 1 OUT5 to rope 5 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 16-18. | ||
xlr-rope-06 |
PSAC10 box 1 → Rope 6 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 1 OUT6 to rope 6 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 20-22. | ||
xlr-rope-07 |
PSAC10 box 1 → Rope 7 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 1 OUT7 to rope 7 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 24-26. | ||
xlr-rope-08 |
PSAC10 box 1 → Rope 8 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 1 OUT8 to rope 8 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 28-30. | ||
xlr-rope-09 |
PSAC10 box 1 → Rope 9 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | broken (confirmed) | now, show | Box 1 OUT9 to rope 9 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 32-34. | ||
xlr-rope-10 |
PSAC10 box 1 → Rope 10 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 1 OUT10 to rope 10 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 36-38. | ||
xlr-rope-11 |
PSAC10 box 2 → Rope 11 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 2 OUT1 to rope 11 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 40-42. | ||
xlr-rope-12 |
PSAC10 box 2 → Rope 12 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 2 OUT2 to rope 12 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 44-46. | ||
xlr-rope-13 |
PSAC10 box 2 → Rope 13 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 2 OUT3 to rope 13 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 48-50. | ||
xlr-rope-14 |
PSAC10 box 2 → Rope 14 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 2 OUT4 to rope 14 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 52-54. | ||
xlr-rope-15 |
PSAC10 box 2 → Rope 15 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 2 OUT5 to rope 15 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 56-58. | ||
xlr-rope-16 |
PSAC10 box 2 → Rope 16 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 2 OUT6 to rope 16 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 60-62. | ||
xlr-rope-17 |
PSAC10 box 2 → Rope 17 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | broken (confirmed) | now, show | Box 2 OUT7 to rope 17 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 64-66. | ||
xlr-rope-18 |
PSAC10 box 2 → Rope 18 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 2 OUT8 to rope 18 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 68-70. | ||
xlr-rope-19 |
PSAC10 box 2 → Rope 19 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 2 OUT9 to rope 19 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 72-74. | ||
xlr-rope-20 |
PSAC10 box 2 → Rope 20 | xlr | 4-pin XLR: 24 V + SPI data (UCS1903) | one-way | working (confirmed) | now, show | Box 2 OUT10 to rope 20 (via the supplied XLR to round-plug adapter). Payload: 400 px RGB from universes 76-78. | ||
hdmi-led |
bitcube → Colorlight sender | video | HDMI 1920x1080 (display LED 1920X1080) | one-way | 60 fps | working (confirmed) | now, show | TouchDesigner’s black full-screen window on the LED display; the panel atlas sits at its top-left. Payload: atlas 512x128 at (0,0): quad 1 x 0-255, quad 2 x 256-511. | |
colorlight-rx |
Colorlight sender → Colorlight 5A-75B receive card | video | Colorlight protocol over Ethernet cable | one-way | working (unverified) | now, show | Sender to the 5A-75B receive card. | ||
hub75-q1 |
Colorlight 5A-75B receive card → HUB75 quad 1 | video | HUB75 ribbon | one-way | working (confirmed) | now, show | Receive card to quad 1 (four 128x64 panels). | ||
hub75-q2 |
Colorlight 5A-75B receive card → HUB75 quad 2 | video | HUB75 ribbon | one-way | blocked (awaiting:Andi) | now, show | Receive card to quad 2: not connected yet. | ||
hub75-rest |
Colorlight 5A-75B receive card → Other HUB75 panels | video | HUB75 ribbon | one-way | planned (unverified) | now, show | The other panels, if they join the show. | ||
usbc-m14 |
bitcube → M14 monitor | video | USB-C DisplayPort | one-way | working (confirmed) | now, show | Primary display: desktop, taskbar, the status page in Chrome. | ||
wifi-bitcube |
bitcube → Agape eero | lan | Wi-Fi (Agape WiFi, 5 GHz) | two-way | working (confirmed) | now | bitcube’s internet and house LAN: 10.0.7.79, default route. | ||
uplink-bitcube-gallery |
bitcube → Gray Area gallery network | lan | Wi-Fi or Ethernet 2 (gallery network) | two-way | planned (unverified) | show | bitcube’s internet at Gray Area (dual-NIC plan: lighting on one NIC, internet on the other). | ||
eero-internet |
Agape eero → Public internet | lan | ISP uplink | two-way | working (confirmed) | now, show | Agape’s internet. | ||
gallery-internet |
Gray Area gallery network → Public internet | lan | Gallery ISP uplink | two-way | planned (unverified) | show | Gray Area’s internet. | ||
glinet-starlink |
GL.iNet router → Starlink | starlink | Ethernet to the Starlink router | two-way | unknown (unverified) | now, show | The river kit’s uplink: the GL.iNet behind Starlink at the river (unverified). | ||
starlink-internet |
Starlink → Public internet | starlink | Starlink satellite (CGNAT, outbound only) | two-way | planned (unverified) | now, show | The river’s internet. | ||
cam-river-lan |
River cam → GL.iNet router | lan | LAN 192.168.8.0/24 | two-way | working (confirmed) | now, show | River cam at 192.168.8.217 behind the GL.iNet. | ||
cam-gallery-wifi |
Gallery cam → Agape eero | lan | Wi-Fi (Agape WiFi, DHCP 10.0.7.76) | two-way | working (confirmed) | now | Gallery cam on the house network. | ||
cam-gallery-galnet |
Gallery cam → Gray Area gallery network | lan | Wi-Fi (gallery network) | two-way | planned (unverified) | show | Gallery cam on Gray Area’s network. | ||
rtsp-gallery |
bitcube → Gallery cam | lan | RTSP over TCP 554 (/Preview_01_main) | one-way (bitcube pulls) | camera admin password (secrets.local.json -> cameras.gallery) | on demand; RiverSnapshot every 15 min | working (confirmed) | now, show | Video from the gallery cam: snapshots, calibration (structured light), the status-page snapshot, and (planned) clips for the agent. Path: now: Agape WiFi, direct. Teammates on Will’s tailnet reach it through bitcube’s 10.0.7.76/32 route. show: gallery network (planned). |
ptz-gallery |
bitcube → Gallery cam | lan | HTTPS 443 JSON API /cgi-bin/api.cgi | one-way | camera admin password -> session token | by hand (about 2,600 px/s of pan; nudge 0.1-0.4 s) | working (confirmed) | now, show | Pan/tilt: Login -> token -> PtzCtrl -> Stop -> Logout (tools/cameras/ptz.py). |
rtsp-river |
bitcube → River cam | tailscale | RTSP over TCP 554, via Tailscale subnet route 192.168.8.0/24 | one-way (bitcube pulls) | camera admin password (secrets.local.json -> cameras.river) | unknown (confirmed) | now, show | Video from the river cam, through Will’s tailnet and the GL.iNet. Path: bitcube -> Tailscale (Will’s tailnet) -> GL.iNet subnet route 192.168.8.0/24 -> 192.168.8.217, now at the river over Starlink (unverified). | |
ptz-river |
bitcube → River cam | tailscale | HTTPS 443 JSON API via Tailscale | one-way | camera admin password -> session token | unknown (confirmed) | now, show | Pan/tilt for the river cam. | |
ts-bitcube |
bitcube → Will’s tailnet (tail03bc1a) | tailscale | WireGuard (UDP 41641) | two-way | working (confirmed) | now, show | bitcube is a member of Will’s tailnet (100.73.68.60), advertises the gallery cam (10.0.7.76/32) and accepts routes. | ||
ts-glinet |
GL.iNet router → Will’s tailnet (tail03bc1a) | tailscale | WireGuard; subnet router | two-way | unknown (confirmed) | now, show | The GL.iNet advertises 192.168.8.0/24 (the river cam) into Will’s tailnet. Its 10.0.7.0/24 route was un-approved on 18 Oct 17:47. | ||
ts-andi |
Andi’s PC (estrogen) → Will’s tailnet (tail03bc1a) | tailscale | WireGuard | two-way | working (confirmed) | now, show | Andi’s PC on Will’s tailnet. | ||
ts-smelterplume |
smelterplume (Mac) → Will’s tailnet (tail03bc1a) | tailscale | WireGuard | two-way | working (confirmed) | now, show | smelterplume on Will’s tailnet. | ||
ts-dave |
Dave’s computer → Will’s tailnet (tail03bc1a) | tailscale | WireGuard | two-way | unknown (unverified) | now, show | Dave’s access to Will’s tailnet (approved 16 Oct per Signal; no device seen 18 Oct). | ||
ts-mini |
Gavin’s Mac mini → Gavin’s tailnet (tailddc624) | tailscale | WireGuard | two-way | working (confirmed) | now, show | The mini on Gavin’s tailnet (also its subnet router for 10.0.7.0/24). | ||
ts-air |
Gavin’s MacBook Air → Gavin’s tailnet (tailddc624) | tailscale | WireGuard | two-way | working (confirmed) | now, show | The Air on Gavin’s tailnet. | ||
ts-share |
Will’s tailnet (tail03bc1a) → Gavin’s tailnet (tailddc624) | tailscale | Tailscale node share | one-way | working (confirmed) | now, show | bitcube is shared from Will’s tailnet into Gavin’s (accepted 17 Oct). Subnet routes are not shared. | ||
ssh-mini |
Gavin’s Mac mini → bitcube | tailscale | SSH, TCP 22 | one-way | SSH key ~/.ssh/id_ed25519 (public half in administrators_authorized_keys) | working (confirmed) | now, show | ssh bitcube from the mini: docs sessions, reading state, running PowerShell. |
|
vnc-air |
Gavin’s MacBook Air → bitcube | tailscale | VNC, TCP 5900 (macOS Screen Sharing) | one-way | TightVNC password | working (confirmed) | now, show | Shows the real console and HDMI output. | |
rdp-air |
Gavin’s MacBook Air → bitcube | tailscale | RDP, TCP 3389 (Windows App) | one-way | Windows password | working (confirmed) | now, show | Remote desktop. Blanks the HUB75 while connected; hand the session back afterwards. | |
smb-air |
Gavin’s MacBook Air → bitcube | tailscale | SMB, TCP 445 | one-way | Windows password | working (confirmed) | now, show | Finder -> smb://100.73.68.60/River. | |
remote-andi |
Andi’s PC (estrogen) → bitcube | tailscale | RDP 3389 / VNC 5900 | one-way | Windows or VNC password | working (unverified) | now, show | Andi’s remote access to bitcube over Will’s tailnet. | |
ssh-dave |
Dave’s computer → bitcube | tailscale | SSH, TCP 22 | one-way | Dave’s SSH key (comment river) | unknown (unverified) | now, show | Dave’s SSH access: user@bitcube.tail03bc1a.ts.net. | |
git-bitcube |
bitcube → gavaiken/river-bitcube | git | git over SSH (github.com:22) | one-way | deploy key C:\Users\user.ssh\river_bitcube_deploy (core.sshCommand) | working (confirmed) | now, show | Claude on bitcube commits and pushes to main. | |
snapshot-push |
bitcube → snapshot branch | git | git over SSH (force-push, one parentless commit) | one-way | deploy key river_bitcube_deploy | every 15 min, 08:00-02:00 Pacific | working (confirmed) | now, show | RiverSnapshot pushes the latest gallery.jpg. |
git-mini-bitcube |
Gavin’s Mac mini → gavaiken/river-bitcube | git | git (GitHub, gavaiken) | two-way | working (confirmed) | now, show | Coordinator and docs sessions pull and push. | ||
git-mini-agent |
Gavin’s Mac mini → drocamor/river-agent | git | git (GitHub, gavaiken, push access) | two-way | working (confirmed) | now, show | Gavin’s clone of Dave’s repo; bitcube’s copy is synced from here. | ||
pages-build |
gavaiken/river-bitcube → Status site (GitHub Pages) | git | GitHub Pages build (main:/docs, Jekyll) | one-way | working (confirmed) | now, show | Every push to main rebuilds the public site. | ||
status-page |
bitcube → Status site (GitHub Pages) | https | HTTPS | one-way (bitcube reads) | working (confirmed) | now, show | At login Chrome opens the status page on the M14 (local docs if offline). | ||
actions-deploy |
drocamor/river-agent → river-agent app | git | GitHub Actions -> modal deploy | one-way | MODAL_TOKEN_ID / MODAL_TOKEN_SECRET (GitHub Actions secrets) | working (confirmed) | now, show | A push to main deploys the app. | |
sync-pull |
bitcube → GET /data/{file} | https | HTTPS GET, every 3 s | one-way (bitcube pulls) | none (SITE_TOKEN not set); with SITE_TOKEN: cookie river_token | 5 files every 3 s (backs off to 6 s after 10 failures) | planned (awaiting:Dave) | now, show | gallery/sync.py on bitcube mirrors status.json, state.json, pad.json, journal_recent.json and reflex.json into a local folder and serves them on 127.0.0.1:8770, so the show keeps its last scene when the internet drops. |
canvas-webrender |
bitcube → GET /canvas.html | https | HTTPS (TouchDesigner Web Render TOP) | one-way (bitcube pulls) | stopped (confirmed) | now, show | river.toe’s Web Render TOP loads canvas.html straight from Modal (500x800). Off in the running rain file; planned to switch to the local mirror on :8770. | ||
ingest-gallery |
bitcube → POST /ingest/{location}/{channel} | https | HTTPS POST /ingest/gallery/video (video/mp4) | one-way (bitcube pushes) | Authorization: Bearer |
one 10 s clip every 10 s | planned (awaiting:Dave) | now, show | capture.py on bitcube records 10 s gallery-cam clips (640 px, 10 fps, H.264 + AAC) back to back and posts each one. |
ingest-river |
River cam → POST /ingest/{location}/{channel} | https | HTTPS POST /ingest/river/video via a capture agent | one-way (push) | Authorization: Bearer |
planned (unverified) | show | River clips reach Modal through a capture agent: bitcube pulling RTSP over Tailscale, or a device at the river. Not decided. | |
web-volume |
GET /data/{file} → Volume river-agent-data | internal | Modal Volume mount | one-way | working (confirmed) | now, show | Served from the Volume (reloaded at most every 2 s). | ||
ingest-volume |
POST /ingest/{location}/{channel} → Volume river-agent-data | internal | Modal Volume mount | one-way | working (confirmed) | now, show | Stores media/ |
||
app-canvas |
river-agent app → GET /canvas.html | internal | web function | one-way | working (confirmed) | now, show | Served by the web function from web/. | ||
app-page |
river-agent app → GET / (journal page) | internal | web function | one-way | working (confirmed) | now, show | Served by the web function. | ||
app-data |
river-agent app → GET /data/{file} | internal | web function | one-way | working (confirmed) | now, show | Served by the web function. | ||
app-ingest |
river-agent app → POST /ingest/{location}/{channel} | internal | web function | one-way | working (confirmed) | now, show | Handled by the web function. | ||
app-volume |
river-agent app → Volume river-agent-data | internal | Modal Volume mount | two-way | working (confirmed) | now, show | tick and watch read media and write the journal, state, status and reflexes. | ||
tick-llm |
river-agent app → DeepSeek-V4-Flash (vLLM) | https | HTTPS /chat/completions (OpenAI-compatible) | one-way | VLLM_API_KEY + Modal proxy auth (MODAL_PROXY_KEY / MODAL_PROXY_SECRET) | one tick every 5 min, 09:00-01:55 Central | working (confirmed) | now, show | Each tick’s reasoning and tool calls. |
tick-perception |
river-agent app → Perception (vision) | https | HTTPS | one-way | VLLM_API_KEY + Modal proxy auth | working (confirmed) | now, show | Frames to text for the tick. | |
watch-clef |
river-agent app → Clef-Omni | internal | Modal Cls.remote() call | one-way | working (confirmed) | now, show | watch() puts each clip to the agent’s questions. | ||
tick-weather |
river-agent app → Open-Meteo | https | HTTPS GET | one-way | none | working (confirmed) | now, show | Current weather near the river, each tick. | |
cam-via-bitcube |
Will’s tailnet (tail03bc1a) → Gallery cam | tailscale | subnet route 10.0.7.76/32 via bitcube | one-way | working (confirmed) | now | Devices on Will’s tailnet reach the gallery cam (RTSP 554, PTZ HTTPS 443) through bitcube, which advertises its address. Not visible from Gavin’s tailnet: node sharing doesn’t carry routes. | ||
cam-via-bitcube-show |
Will’s tailnet (tail03bc1a) → Gallery cam | tailscale | subnet route |
one-way | planned (unverified) | show | At Gray Area: bitcube advertises the gallery cam’s new address the same way (and drops 10.0.7.76/32), so the team and Modal-side tools on the tailnet can reach it. |
Modal composes, bitcube renders. Every 5 minutes the agent’s tick writes a scene (state.json) and status; between ticks, camera reflexes nudge the scene’s knobs (reflex.json). bitcube never renders on Modal: it mirrors these small JSON files locally (gallery/sync.py on 127.0.0.1:8770) and draws the scene itself (canvas.html in TouchDesigner’s Web Render TOP, sampled onto the ropes and HUB75), so the show keeps its last scene through an outage. In the other direction, bitcube pushes 10 s camera clips to /ingest with a bearer token. Nothing on Modal connects into bitcube or the tailnet.
Base URL https://to-be-a-river--river.modal.run. 2026-10-18 17:21 PT: /healthz and /data/* answer without a token (SITE_TOKEN is not set). Examples below are live from that check, trimmed.
| Item | State | Note |
|---|---|---|
| bitcube pulls /data/state.json, reflex.json and status.json (plus pad.json and journal_recent.json) every 3 s through the gallery/sync.py mirror, served on 127.0.0.1:8770 (8765 is RopeLights). | awaiting:Dave | The code supports it (–port 8770 –every 3). Dave to OK running it on bitcube (open question). |
| File names, fields and meanings below. | confirmed | From river-agent a9cbd2c and the live site. |
| If Modal or the internet is down, the mirrored files stop changing: the last scene keeps playing; evolution resumes when the link returns. | confirmed | |
| /data and the page need no token today. If Dave sets SITE_TOKEN, sync.py sends it as the river_token cookie (–token). | confirmed | Verified 18 Oct 17:21 PT. |
| bitcube pushes gallery-cam clips: POST /ingest/gallery/video, Content-Type video/mp4, Authorization: Bearer |
awaiting:Dave | Needs INGEST_TOKEN, and Dave’s confirmation of location/channel names and cadence. |
| River clips: POST /ingest/river/video from whichever capture agent reaches the river cam. | awaiting:Dave | Who runs it is open (Will/Dave). |
| TouchDesigner renders http://127.0.0.1:8770/canvas.html?w= |
awaiting:Dave | Design size (w x h) for 20x400 ropes + panels: open question. |
GET /data/{file} (confirmed)| Status | Response |
|---|---|
| 200 | the file, Cache-Control: no-store |
| 401 | unauthorized (only if SITE_TOKEN is set) |
| 404 | not a served file, or not written yet (state.json is never 404: a placeholder scene is served) |
POST /ingest/{location}/{channel} (confirmed)| Status | Response |
|---|---|
| 200 | {“ok”: true, “stored”: “media/gallery/video/2026-10-22T19-00-10-123Z.mp4”, “bytes”: 412345, “pruned”: 3} |
| 400 | {“ok”: false, “error”: “location and channel must be short lowercase names”} |
| 401 | {“ok”: false, “error”: “bad token”} |
| 413 | {“ok”: false, “error”: “body larger than 26214400 bytes”} |
| 415 | {“ok”: false, “error”: “unknown content type ‘text/plain’; send image/jpeg, audio/wav, application/json, or ?ext=”} |
| 503 | {“ok”: false, “error”: “storage busy, retry: …”, “headers”: “Retry-After: 30”} |
curl -X POST -H "Authorization: Bearer $INGEST_TOKEN" -H "Content-Type: video/mp4" --data-binary @clip.mp4 https://to-be-a-river--river.modal.run/ingest/gallery/video
Reference client:
river-agent capture/capture.py --site https://to-be-a-river--river.modal.run --token $INGEST_TOKEN --location gallery --video rtsp://admin:<password>@10.0.7.76:554/Preview_01_main
A JSON sensor reading:
{
"source_id": "river_temp_01",
"source_type": "water_temperature",
"location": "river",
"timestamp": "2026-10-18T12:00:00-07:00",
"summary": "Water is 11.2 C, down 0.3 since the morning.",
"metrics": {
"celsius": 11.2
}
}
GET /canvas.html?w=&h= (confirmed)GET / (confirmed)status.jsonWritten by tick, at the start (in_progress) and end of every tick; read every 3 s.
| Field | Meaning |
|---|---|
tick |
int, tick number |
ok |
bool |
error |
string or null |
started_at / finished_at |
ISO time |
*_epoch |
Unix seconds |
duration_s |
float |
tick_interval_seconds |
300 |
schedule |
the tick’s cron |
next_tick_epoch |
when the next tick is due |
mood |
one word from the journal |
acted |
the tools the agent called this tick |
scene |
animation, palette_name, colors, placeholder |
pad |
chord and notes |
sensors |
per site_channel: present, file, age_s, fresh |
in_progress |
null, or set while a tick runs |
{
"tick": 3356,
"mode": "once",
"host": "modal",
"model": "deepseek-ai/DeepSeek-V4-Flash",
"ok": true,
"error": null,
"started_at": "2026-10-19T00:20:06+00:00",
"finished_at": "2026-10-19T00:21:36+00:00",
"started_at_epoch": 1792369206.6990602,
"finished_at_epoch": 1792369296.1452744,
"duration_s": 89.4,
"llm_seconds": 87.9,
"tick_interval_seconds": 300.0,
"schedule": "*/5 9-23,0-1 * * *",
"next_tick_epoch": 1792369500.0,
"journal_entries": 3356,
"mood": "expansive",
"journal_written": true,
"acted": [
{
"name": "set_visual_scene",
"animation": "flow",
"colors": [
"#442222",
"#553333",
"#775544",
"#997755",
"#887766"
],
"palette_name": "dry-bones",
"speed": 0.31,
"density": 0.5,
"brightness": 0.44,
"turbulence": 0.68,
"transition_seconds": 90.0,
"placeholder": false
}
],
"scene": {
"animation": "flow",
"palette_name": "dry-bones",
"colors": [
"#442222",
"#553333",
"#775544",
"#997755",
"#887766"
],
"placeholder": false
},
"pad": {
"chord": "IV (D)",
"notes": [
"F#3",
"A3",
"D4"
],
"colour": "add9",
"register": "middle",
"voices": 3,
"intensity": 0.46
},
"sensor_backend": "media",
"sensors": {
"river_camera": {
"present": true,
"file": "2026-10-15T22-22-51-715Z.jpg",
"age_s": 266323,
"fresh": false
},
"river_microphone": {
"present": true,
"file": "2026-10-15T22-22-51-715Z.wav",
"age_s": 266323,
"fresh": false
},
"river_hydrophone": {
"present": false
},
"gallery_camera": {
"present": true,
"file": "2026-10-15T22-22-55-432Z.jpg",
"age_s": 266320,
"fresh": false
},
"gallery_microphone": {
"present": true,
"file": "2026-10-15T22-22-55-432Z.wav",
"age_s": 266320,
"fresh": false
},
"river_weather": {
"present": true,
"fresh": true,
"age_s": 0
}
},
"in_progress": null
}
state.jsonWritten by tick, when the agent calls set_visual_scene; read every on a new tick (or every 30 s).
| Field | Meaning |
|---|---|
seq |
int, +1 per change (apply only when it changes) |
set_at / set_at_epoch |
when it was set (used to ease) |
tick |
int |
scene.animation |
drift | flow | ripples | aurora | embers | waves | blooms | pulse |
scene.colors |
2-6 lowercase #rrggbb |
scene.palette_name |
string (<= 60) or null |
scene.speed / density / turbulence |
0-1 |
scene.brightness |
0.3-1 |
scene.transition_seconds |
5-300 (default 60) |
scene.placeholder |
bool |
animations |
the allowed animation names |
{
"seq": 1542,
"set_at": "2026-10-18T17:21:35-07:00",
"set_at_epoch": 1792369295.5599794,
"tick": 3356,
"scene": {
"animation": "flow",
"colors": [
"#442222",
"#553333",
"#775544",
"#997755",
"#887766"
],
"palette_name": "dry-bones",
"speed": 0.31,
"density": 0.5,
"brightness": 0.44,
"turbulence": 0.68,
"transition_seconds": 90.0,
"placeholder": false
},
"animations": [
"drift",
"flow",
"ripples",
"aurora",
"embers",
"waves",
"blooms",
"pulse"
]
}
reflex.jsonWritten by watch, every few seconds while a camera is answering; read every 2 s.
| Field | Meaning |
|---|---|
updated_epoch |
Unix seconds |
ttl_seconds |
ignore the file once older than this (default 90) |
offsets |
brightness, speed, density, turbulence; each clamped to +-0.25, added to the scene’s knobs |
active |
which reflexes contributed |
sites |
last reading per site (epoch or null) |
reflexes_set_by |
agent or default |
{
"updated_epoch": 1792103017.19985,
"updated_at": "2026-10-15T22:23:37+00:00",
"ttl_seconds": 90.0,
"offsets": {
"brightness": 0.0,
"speed": 0.0,
"density": 0.0,
"turbulence": 0.0
},
"active": [],
"sites": {
"river": 1792103014.1969469,
"gallery": 1792103017.19985
},
"reflexes_set_by": "agent"
}
pad.jsonWritten by tick (play_pad); read every 3 s. The installation will probably have no sound.
| Field | Meaning |
|---|---|
seq |
int |
key |
e.g. A major |
state |
chord, notes (MIDI), colour (plain|seventh|suspended|open|add9), register (low|middle|high), voices, intensity |
events |
timed voice moves: expression / on / off at absolute epochs |
{
"seq": 1112,
"set_at": "2026-10-18T17:21:36-07:00",
"set_at_epoch": 1792369296.0530698,
"tick": 3356,
"key": "A major",
"channel": 10,
"backend": "schedule",
"state": {
"colour": "add9",
"register": "middle",
"voices": 3,
"intensity": 0.46,
"transition_seconds": 150.0,
"notes": [
54,
57,
62
],
"degree": 3,
"chord": "IV (D)",
"note_names": [
"F#3",
"A3",
"D4"
]
},
"from_notes": [
56,
62
],
"velocity": 71,
"expression": 69,
"events": [
{
"at": 1792369296.053,
"type": "expression",
"value": 69
},
{
"at": 1792369346.053,
"type": "off",
"note": 56
},
{
"at": 1792369346.453,
"type": "on",
"note": 54,
"velocity": 71
}
]
}
journal_recent.jsonWritten by tick; read every 3 s.
| Field | Meaning |
|---|---|
count |
total journal entries |
shown |
entries here (up to 50) |
entries |
oldest first: tick, timestamp, reflection, mood_word, auto_generated, context |
{
"count": 3356,
"shown": 50,
"generated_at_epoch": 1792369296.2,
"entries": [
{
"tick": 3356,
"timestamp": "2026-10-18T17:21:35-07:00",
"reflection": "(the agent's journal text)",
"mood_word": "expansive",
"auto_generated": false
}
]
}
No values, ever. Ask the holder.
| Secret | Unlocks | Lives in | Holder | Note |
|---|---|---|---|---|
| Reolink admin password | Both cameras: RTSP video and the PTZ API (user admin) | bitcube: river-bitcube\secrets.local.json (untracked) -> cameras.gallery and cameras.river, inside the RTSP URLs; the Reolink app | Will Gardiner | |
bitcube Windows password (user user) |
Console, RDP, SMB | Will; bitcube: LSA secret (Sysinternals Autologon) for auto-login | Will Gardiner | SSH uses keys, not this password. |
| TightVNC password | VNC on bitcube :5900 | TightVNC server settings on bitcube | unverified (set 17 Oct by Gavin/Andi) | |
| INGEST_TOKEN | POST /ingest on Modal | Modal secret river-agent-web; bitcube: secrets.local.json once Dave shares it (session E) | Dave | |
| SITE_TOKEN | The page and /data when set | Modal secret river-agent-web (optional) | Dave | Not set (verified 18 Oct 17:21 PT). |
| VLLM_API_KEY | The model endpoints | Modal secret river-agent-api-key | Dave | |
| MODAL_PROXY_KEY / MODAL_PROXY_SECRET | Modal proxy auth in front of the model endpoints | Modal secret river-agent-proxy | Dave | |
| MODAL_TOKEN_ID / MODAL_TOKEN_SECRET | Deploying to the Modal workspace | GitHub Actions secrets on drocamor/river-agent | Dave | |
| river-bitcube deploy key | Push to gavaiken/river-bitcube only | bitcube: C:\Users\user.ssh\river_bitcube_deploy (private half); GitHub repo Settings -> Deploy keys (public half) | Gavin Aiken | Revoke under the repo’s Deploy keys. |
| SSH keys into bitcube | SSH as user (admin) |
Gavin’s mini ~/.ssh/id_ed25519; Dave’s computer (key comment river); public halves: C:\ProgramData\ssh\administrators_authorized_keys | gavin, dave | |
| Project Tailscale account itsriverbox@outlook.com | Will’s tailnet admin console | Will | Will Gardiner | bitcube’s node key expires 2027-04-11. |
| GL.iNet admin password | The camera router’s admin UI | Will | Will Gardiner | unverified |
| TeamViewer credentials | TeamViewer remote access to bitcube | unknown (2023 BitCube setup) | unverified |
Ask Dave
q-site-token, in plan.md)q-ingest-names, in plan.md)q-sync-ok, in plan.md)q-canvas-size, in plan.md)q-oct15-ingest)q-dave-tailnet)Ask Andi Doud
q-quad2, in plan.md)Ask Will Gardiner
q-ropelights, in plan.md)q-gallery-network, in plan.md)q-gallery-cam-ip, in plan.md)q-river-capture)q-box2-feed)q-smelterplume)Ask the team
q-fallback, in plan.md)Ask Gavin Aiken
q-td-licence, in plan.md)| Who | Role | Accounts |
|---|---|---|
| Colin | Artist; lead and organiser, ordering. Also: TouchDesigner as the single renderer, per-rope sample paths | |
| Justine Sun Dela Cruz | Meeting notes, visual references, coordination | |
| Will Gardiner | Networking, cameras, Tailscale (runs the project tailnet), Windows box, RopeLights app | Tailscale: itsriverbox@outlook.com (Project account Will created; owns tailnet tail03bc1a (shows as “River B”)) |
| Dave | The agent and its generated visuals on Modal; owns river-agent | GitHub: drocamor; Modal: workspace to-be-a-river (Workspace owner unverified) |
| Andi Doud | HUB75 panels, Colorlight config, TouchDesigner | Tailscale: andixdoud@gmail.com (Member of Will’s tailnet) |
| Lilian (Lily) | Rigging; Gray Area liaison | |
| Gavin Aiken | Pixel mapping, TouchDesigner harness, this repo and its Claude sessions | GitHub: gavaiken; Tailscale: gav.aiken@gmail.com (Own tailnet tailddc624; Admin on Will’s tailnet; bitcube is shared into his tailnet) |
ssh-bitcube: Read-only PowerShell over ssh bitcube: OS, NICs, IPs, routes, monitors, listening ports, services, scheduled tasks, SMB shares, firewall rules, ping of cameras and PSAC10s (2026-10-18 17:15 PT)tailscale-bitcube: tailscale status and tailscale status --json on bitcube (Will’s tailnet view) (2026-10-18 17:16 PT)tailscale-mini: tailscale status on Gavin’s Mac mini (Gavin’s tailnet view) (2026-10-18 17:17 PT)rope-config: Will’s C:\Users\user\RopeLights\config.local.json on bitcube (contains no secrets) (2026-10-18 17:16 PT)modal-live: Unauthenticated GET of https://to-be-a-river–river.modal.run/healthz and /data/{status,state,reflex,pad}.json (all 200, so SITE_TOKEN is not set) (2026-10-18 17:21 PT)river-agent: drocamor/river-agent at a9cbd2c: modal_deploy/app.py, modal_deploy/agent.env, gallery/sync.py, capture/capture.py, tools/site.py, web/canvas.js, tools/visual_scene.py, tools/fast_perception.py, tools/midi_pad.py, .github/workflows (2026-10-18)log: docs/a-river-becoming-log.md (session history) https://gavaiken.github.io/river-bitcube/a-river-becoming-log.htmlhardware-doc: docs/a-river-becoming-hardware.md (verified hardware) https://gavaiken.github.io/river-bitcube/a-river-becoming-hardware.htmlclaude-md: CLAUDE.md (facts and gotchas for bitcube) https://github.com/gavaiken/river-bitcube/blob/main/CLAUDE.mdplan: docs/plan.md (checklist, decisions, open questions) https://gavaiken.github.io/river-bitcube/plan.htmlrope-map: Camera rope identification, 18 Oct 14:11 (docs/img/rope-map-2026-10-18.png) https://gavaiken.github.io/river-bitcube/img/rope-map-2026-10-18.pngpixelmap: tools/pixelmap/pixelmap-2026-10-18.json (structured-light map, session B)signal: The team’s Signal thread (exported to 17 Oct on Gavin’s mini; not public)session-a: Session A notes, 18 Oct 17:50 and 17:55 (docs/sessions/A.md): GL.iNet to the river, route changes, gallery cam via bitcube, HUB75 gain https://gavaiken.github.io/river-bitcube/sessions/A.htmldocs/system/system.json.python3 tools/system_docs.py to regenerate this file..venv/bin/pytest tools/tests (structure, secret scan, real secrets from bitcube, this file in sync).