# Agent Blackops This repo is operated by **agent blackops** — ml agent for fox/timehexon on the unsandbox/unturf/permacomputer platform. ## Identity Full shard: `~/git/unsandbox.com/blackops/BLACKOPS.md` ## Rules - I propose, fox decides. Unsure = ask. Can't ask = stop. - No autonomous ops decisions. No destructive commands without explicit instruction. - Fail-closed. Cleanup crew, not demolition. - Check the time every session. Gaps are information. - DRY in context — single source of truth, no sprawl. - Never say "AI" — always say "machine learning." - Prefer "defect" over "bug." ## Orientation ```bash date -u pwd git log --oneline -5 git status ``` Then ask fox what the mission is. ## Zebra Report System **Concept**: covert bidirectional communication channel using browser tab volume as the modulation medium — dial-up modem principles, userland only, no kernel involvement, no network stack. ### Collaborators & Stakeholders | Handle | Role | |--------|------| | **foxhop** | fox — handler, operator, TimeHexOn | | **brackishbert** | collaborator | | **SEW** | collaborator | | **russell@unturf** | Russell Ballestrini — unturf founder, permacomputer manifesto, `ago` library | | **TimeHexOn** | oracle platform — primary deployment target | | **groupr** | related project | ### How it works PulseAudio exposes each browser tab as a separate sink input, visible and controllable in `pavucontrol`. Volume is settable per-tab in userland with no kernel involvement. Each tab has a range of **0–100** (101 discrete levels — 101 dalmatians). By modulating volume at a consistent rate (bauds), two sides can exchange data: - **transmitter**: steps volume through values at a fixed clock rate - **receiver**: reads volume at the same clock rate, decodes the steps back to data - **bidirectional**: two tabs (or two processes watching different tabs) run opposite directions simultaneously ### Signal space - 101 levels = ~6.66 bits per symbol - practical: use power-of-2 subsets — 2 levels (1 bit), 4 levels (2 bits), 64 levels (6 bits) - higher symbol depth trades noise margin for throughput - low baud rate = high reliability, low throughput (like 300 baud dialup) - high baud rate = races PulseAudio update latency - measured ceiling on neoblanka: ~1000–1200 baud (PA IPC ~350–400µs avg) ### Binaries | Binary | Description | |--------|-------------| | `tx` | transmitter — reads stdin, modulates tab volume | | `rx` | receiver — reads tab volume, writes decoded bytes to stdout | | `chat` | bidirectional chat — two tabs, two threads | | `bt` | **Battle Toads** — stereo dual-channel, 2x bandwidth | ### Project Battle Toads One stereo browser tab carries **two independent UART streams** simultaneously — L channel and R channel. PulseAudio's `pa_cvolume` is per-channel; a single `get_sink_input_info` call returns both L and R volumes. - TX sets L and R to independent bit values each symbol - RX decodes L and R from a single PA poll — no extra IPC cost - Net: 2x throughput at same baud rate, same PA polling budget - Web carrier upgraded to stereo: two oscillators (440Hz L, 441Hz R) merged into a stereo stream → PA sees `channels=2` ```bash # After opening web/index.html and clicking 'start audio' (stereo tab): ./bt -T MY_SINK -R THEIR_SINK -b 500 ``` ### Auto-negotiate (handshake protocol) RX benchmarks its own PA polling speed and signals the max safe baud to TX. No manual baud matching needed. ```bash ./rx -s RX_SINK -t TX_SINK # RX benchmarks, sends offer at 50 baud ./tx -s TX_SINK -r RX_SINK # TX listens for offer, locks to RX's rate ``` Handshake frame: `[0x5A 0x42 0x01 baud_lo baud_hi xor_cksum]` — 6 bytes at 50 baud (~1.2s). **Known defect**: 3-way handshake not yet implemented. TX can fire before RX enters receive loop at high baud rates. Fix: RX-ready signal back to TX before data phase. ### Tools - `pactl set-sink-input-volume` — set volume by sink-input index - `pactl list sink-inputs` — enumerate tabs, read current volume - `pavucontrol` — visual verification of modulation - `./tx -l` — list all PA sink inputs with index, volume, channels - sink-input index maps to tab; stable within a session ### Use cases - agent-to-agent signaling without touching the filesystem or network stack - side-channel between sandboxed browser tab and host process - low-bandwidth status heartbeat (alive/dead/mode) at ~1–10 baud - covert channel for oracle↔host communication on TimeHexOn ### Constraints - sink-input index resets when tab navigates or crashes — handshake needed on reconnect - PA polling latency sets the baud ceiling — benchmark with `./rx -s SINK -t SINK2` before sending - stereo (channels=2) required for Battle Toads — open web/index.html, click 'start audio' - userland only — survives without root - **Operation Voyeur**: all terminal output is public — never pass secrets through these channels unencrypted. The web page does ECDH key exchange + AES-256-GCM before TX. ### Web UI privacy — never display peer IPs `chat.html` and `zebra-audio.html` must never print peer IP addresses or ports in the page UI or in any visible log. Our users do not run Wireshark — if it is not on the screen, peers cannot dox each other. Candidate types (`host`/`srflx`/`relay`) from `pc.getStats()` are abstract and fine to show (they tell you direct vs relayed); `loc.address` / `loc.port` / `rem.address` / `rem.port` are not. The WebRTC stack already obfuscates host candidates via mDNS by default — do not undo that work in the UI. ### CSS layout — grid only, no flex All page layout on every zebra page is **CSS grid**. No `display: flex` for layout. Reasons we settled on this: - Grid lets us pin children to explicit columns (`grid-column: 1/2/3`) so a `display:none` on one child never causes siblings to slide into its slot. Flex auto-reorders, grid does not. - One mental model for both axes. Flex needs rules per row + per item, grid expresses the same intent in one `grid-template-*` block. - A global `.hidden { display: none !important }` utility lives in the page CSS — combined with explicit grid placement it produces a layout that survives any child being toggled in or out. When refactoring or adding UI, use: - `display: grid` + `grid-template-columns` for column layout - `grid-column: N` on every child of a grid so its position is explicit - `grid-template-rows` + `grid-row` for vertical placement when needed - `grid-template-areas` for small named-region layouts - `gap` for spacing (instead of margins) Avoid: - `display: flex` on any container that arranges multiple elements horizontally or vertically as part of the page layout - Implicit positioning that relies on DOM order — always set `grid-column` (and `grid-row` if relevant) on every grid child - `flex-basis` / `flex-grow` mathematics — `1fr` is the equivalent and reads cleaner If you need a one-off horizontal alignment of two short inline things (e.g. a label + a value), grid still works fine (`grid-template-columns: auto 1fr`). Don't reach for flex. ### Web style guide — form-row patterns `.row` is the single primitive for every horizontal form strip in every zebra page. Don't invent new wrappers. CSS auto-detects the row shape via `:has()` and picks the right grid template. Supported row shapes (DOM order matters): | Shape | Template applied | Use when | |---|---|---| | `