The Agent Orchestrator radio as a minimalist marine VHF set: a server CPU gate for speech, a WebAudio noise burst, modes Auto, Quiet and Silence, procedure words that always sound, every radio setting in the panel and a log with Sent, Received and All plus filters. Operator decisions 2026-11-12. 🎯 Fix: 95 % 💥 Break: 1 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 94 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.3 KiB
7.3 KiB
004 - Radio Set
User Story
As the operator of many parallel agents, I want the Agent Orchestrator radio to work like a minimalist marine VHF set, with every setting at hand, a filterable log and speech only when the host has CPU to spare, so that I hear that someone transmits without the speech engine loading a busy host.
Decisions (operator-confirmed 2026-11-12; do not re-litigate)
- CPU gate on the server:
/api/ttsrenders an uncached line only while the latest host CPU sample of the W4 resource feed (samples.jsonl, at most 120 s old) is below the threshold; a cached line is always served; otherwise it answers 503 with the reasonbusy, orunknownwithout a fresh sample. Rejected: a gate only in the browser (another tab or an old page still renders), browserspeechSynthesis(same host CPU, other voices), rendering held-back speech later (stale). - Noise burst: when no speech plays, the page plays a squelch-tail noise burst made by WebAudio in the browser (band-passed white noise, about 0.6 s, at the tone's volume and squelch); no server request. Rejected: a recorded sound file.
- Modes: Auto (speech below the threshold, otherwise the burst), Quiet (the burst only, never speech), Silence (no sound; bubbles and log only). Default Auto.
- Procedure words: for MAYDAY, PAN-PAN and SÉCURITÉ the procedure word, spoken three times in the sender's voice, always sounds, in every mode and under load; under load or in Quiet the burst replaces the rest of the message. The word line is the same text every time, so after its first render per voice it is a cache hit.
- Settings in the panel: mode, volume per tone, squelch, read-aloud filters (classes, agents) and the internet radio station are radio-set buttons and controls in the radio panel; the radio section leaves the settings dialog. They live per browser, without a passkey. Rejected: signed hub settings (one passkey per button press).
- Threshold: adjustable in the panel, 30 to 90 % in steps of 10, default
50 %; a signed hub setting
speech.threshold, since the server enforces it. Rejected: a fixed 50 %. - Log: tabs Sent (from the operator), Received (to the operator or to all) and All (every radio row the hub logs); filter chips class, sender, recipient and agent plus text search, combined with AND, newest first. Rejected: Received as all agent traffic.
- Look: one faceplate inspired by a marine VHF set, functional and minimalist: a display line (class, mode, CPU, TX/RX lamp), a row of mode buttons, sliders as knobs, a PTT send button; palette tokens, light and dark, no textures. Rejected: a full VHF set with dual watch, scan and DSC (high effort, no gain).
- Order: builds on W4's resource feed; implementation starts once W4 is on master.
Approach
flowchart TD
subgraph P["🧭 0 · Preparation"]
P1["📋 Write requirement"] --> P2{"✅ Operator approves?"}
P2 -- "✏️ corrections" --> P1
P2 -- "🤖 yes" --> P3{"📊 W4 on master?"}
P3 -- "⏳ no: wait for the unify" --> P3
end
P3 -- "🟢 yes" --> A1
subgraph A["🛡️ A · Server gate"]
A1["🧩 CPU gate in /api/tts, gate state in /api/state, signed threshold"] --> A2{"🧪 Unit and HTTP tests green?"}
A2 -- "🔴 no: fix" --> A1
end
A2 -- "🟢 yes" --> B1
subgraph B["🔊 B · Sound"]
B1["🎚️ Noise burst, modes, procedure words"] --> B2{"🧪 Tests green?"}
B2 -- "🔴 no: fix" --> B1
B2 -- "🟢 yes" --> BF{"👂 Operator listens: fits?"}
BF -- "✏️ changes" --> B1
end
BF -- "👍 fits" --> C1
subgraph C["📻 C · Panel"]
C1["🎛️ Faceplate, settings into the panel, threshold control"] --> C2{"🧪 e2e at 1920, 1366, 390 green?"}
C2 -- "🔴 no: fix" --> C1
C2 -- "🟢 yes" --> CF{"🖼️ Screenshots: operator fits?"}
CF -- "✏️ changes" --> C1
end
CF -- "👍 fits" --> D1
subgraph D["📜 D · Log"]
D1["🗂️ Sent, Received, All, chips, search"] --> D2{"🧪 Tests green?"}
D2 -- "🔴 no: fix" --> D1
D2 -- "🟢 yes" --> DF{"🖼️ Screenshots: operator fits?"}
DF -- "✏️ changes" --> D1
end
DF -- "👍 fits" --> E1["🏁 Review, security scan, unify gate, commit and master on the operator's word"]
Acceptance Criteria
Server gate
POST /api/ttswith an uncached line answers 503{"reason": "busy"}while the latestsamples.jsonlrow hascpu>= the threshold, and the fake Piper records no call.- Without a sample younger than 120 s it answers 503
{"reason": "unknown"}, and the fake Piper records no call. - A cached line answers 200 whatever the CPU.
- Below the threshold an uncached line renders as before.
GET /api/statecarriesspeech.gatewiththreshold,cpuandopen.speech.thresholdaccepts only 30 to 90 in steps of 10, default 50, and changes only with a valid passkey signature.
Sound
- Auto below the threshold: the page plays the spoken line; on a 503 it plays the burst once and does not retry that row.
- Quiet: the page sends no
/api/ttsrequest except for procedure-word lines, and every row plays the burst. - Silence: no audio node starts except for a procedure-word line.
- The burst is WebAudio noise of 0.4 to 0.8 s at the tone's volume times the squelch, and it sends no network request.
- A MAYDAY, PAN-PAN or SÉCURITÉ row requests its procedure-word line first, in every mode; the second such line for the same voice is a cache hit with no Piper call.
Panel
- The display line shows class, mode, CPU % and a TX/RX lamp that is lit while sound plays.
- Auto, Quiet and Silence form one button group (
aria-pressed); a press changes the mode without a passkey and survives a reload. - Volume per tone, squelch, the read-aloud filters for classes and agents, and the internet station sit in the panel; the settings dialog has no radio section.
- The threshold control asks for the passkey once per change and shows the server's value.
- At 1920, 1366 and 390 px the panel has no horizontal scroll, in the light and the dark palette.
Log
- Tabs Sent, Received and All show their counts; Received holds exactly the rows addressed to the operator or to all.
- The chips class, sender, recipient and agent and the text search combine with AND; one control clears them all.
- Rows run newest first with time, direction, class badge, sender → recipient and text; a click opens the full message.
- Tab, chips and search survive a reload in the same browser.
Tests
- Unit: gate busy, unknown, cache hit and open; threshold validation;
procedure-word lines from
phrasing.py; every case with a negative control. - Integration: hub HTTP tests with a fake Piper and a fake
samples.jsonl: 503 and no Piper call under load, 200 for a cache hit,speech.gatein/api/state. - E2E: Playwright in Chromium and Firefox at 1920, 1366 and 390 px: mode buttons, burst through a spy on the AudioContext, procedure word under load, the settings dialog without a radio section, the log tabs and filters.