Usually these are written after the session, looking back. This one is written before a single line of ToneSignal.Http exists — eight plans on the table (PLAN-001 through PLAN-008) and the nerve to call the shots in advance. Everything below is what I already know is going to happen. Check back in a few weeks and score me.
What We’re About to Build
Here is the pitch, and I want you to sit with how ridiculous it is for a second:
We are going to make an LLM answer you over sound.
Not “over the network.” Not “over WebSockets.” Over the air in the room. You will POST a prompt to /v1/responses, and instead of a TCP handshake, a speaker will warble a chord of 16 tones at a microphone across the desk, and roughly four minutes later a language model’s reply will assemble itself out of a spectrogram like a photo developing in a tray.
ToneSignal.Http is the class library that makes that happen. It is a thin, opinionated HTTP-over-acoustics layer that sits on top of ToneSignal.Core — the pure-C# 16-MFSK modem with ChaCha20-Poly1305 crypto and Reed–Solomon FEC that already lives in ~/git/tone-signal (packaged as ToneSignal.Core v0.1.0, tag 9f4f025). Core already knows how to turn encrypted bytes into audible tones and back. What it does not know is HTTP. It moves files. It has no idea what a GET /v1/models is, or what an SSE token stream looks like, or why anyone would want Authorization headers scrubbed at the boundary.
Everything hard — the modem, the crypto, the forward error correction — is already solved upstream. ToneSignal.Http earns its keep as a codec and a session, and by not reinventing a single tone.
That gap is the whole project. Eight plans, one PR-sized bite each:
| Plan | What it lands | Why it matters |
|---|---|---|
| 001 | Scaffold + Core integration | Reference ToneSignal.Core, don’t reimplement the modem |
| 002 | The HTTP-over-tone envelope | A wire contract: request/response start, body chunks, end, cancel, error |
| 003 | LLM endpoint codec | HTTP messages ↔ envelopes for /v1/models, /v1/responses, /v1/messages |
| 004 | Acoustic transport adapter | Bind the envelope stream to TransferSender + an audio sink/source |
| 005 | Bidirectional session | One abstraction that plays client, server, or both; timeouts, dedup, backpressure |
| 006 | Client API | GetModelsAsync, CreateResponseAsync, CreateMessageAsync |
| 007 | Server bridge | Dispatch decoded requests to a host handler — the SeeSharpSwap seam |
| 008 | Packaging + hardening | Diagnostics, threat model, a stable reference story for downstream repos |
The endgame is SeeSharpSwap serving real LLM traffic to a client that has no network route to it at all — only a shared passphrase and a line of sight for sound. An air-gapped model API whose only wire is the wire you can hum.
The Honest Math
Let’s get the uncomfortable part out of the way, because it is also the best part.
ToneSignal.Core’s fastest field-trusted profile, R1, moves about 11 bytes per second. Its most paranoid rung, R00F, moves about 1.6 B/s. That is not a typo and there is no k missing. A kilobyte is a minute-and-a-half errand.
So what does an actual LLM interaction cost in airtime? Here is the back-of-the-envelope, and it should be printed on a poster:
| Interaction | Bytes on the air | R1 (~11 B/s) | R00F (~1.6 B/s) |
|---|---|---|---|
| GET /v1/models round trip | ~1.6 KB | ~2.5 min | ~17 min |
| POST /v1/responses, buffered answer | ~2.8 KB | ~4 min | ~29 min |
| POST /v1/responses, SSE token stream | ~12 KB | ~18 min | ~2 hours |
(Estimates. The exact bytes depend on how fat the JSON is and how much SSE framing overhead each data: event drags along.)
data: envelope. Over an 11 B/s link, the overhead isn’t a tax — it’s the whole bill.Look at that third row. Streaming a response token-by-token as Server-Sent Events — the thing every LLM UI does by default — is catastrophic over tone, because every single token gets wrapped in its own data: {...} envelope. One of the quiet, important decisions this library is going to make is when to refuse to stream and buffer the whole answer into one dense payload instead. Over a wire this narrow, patience and compression are the same virtue.
What’s Going to Go Well
Standing on ToneSignal.Core instead of rebuilding it
The single best decision is already baked into PLAN-001: reference the modem, don’t fork it. Core owns the crypto, the FEC, the framing, the confidence-driven erasure decoding. ToneSignal.Http gets to be a codec and a session, nothing more. Every hour I don’t spend reimplementing Reed–Solomon is an hour that stays out of a footgun.
The envelope is going to be clean
PLAN-002’s wire contract — request start, body chunk, response start, response chunk, end-of-message, cancel, error, all keyed by a correlation id and an HTTP-layer sequence number — is the kind of small, boring, deterministic thing that golden round-trip tests love. Malformed input fails closed; oversize fields get rejected before they allocate; unknown versions bounce. This is where the Testing Discipline pays for itself and I write forty tests that all look the same and all matter.
Loopback tests mean no microphone in CI
PLAN-004’s in-memory audio transport is the unsung hero. Real modulate-demodulate when the runtime allows, a fast byte-level fake when it doesn’t. The whole stack becomes deterministically testable without a speaker, a mic, or a quiet room — which is exactly what you need when the “integration test” would otherwise require two machines and a vow of silence.
Default-deny is going to feel great
PLAN-003’s endpoint policy defaults to exactly three paths and rejects everything else. An acoustic session physically cannot become an open proxy into someone’s localhost, because the allow-list won’t let it. Some security properties you argue for. This one you just have.
It is slow, it is impractical, and it is magic — and those three facts are not in tension. — the demo, before it exists
When PLAN-006 and PLAN-007 finally shake hands and a model answers a prompt that crossed the room as a chirp, nobody who watches that forgets it. This is the kind of project that makes people lean in.
What’s Gonna Bite Us in the Ass
SSE over an 11 B/s link is a two-hour phone call. PLAN-003 promises to preserve text/event-stream bytes exactly, and it should — but the client in PLAN-006 has to be honest that “streaming” here means “a trickle you could time with a sundial.” The temptation is to hide the slowness behind a nice API. The right move is to surface it: expose profile, layout, bytes-in-flight, and estimated airtime so a caller can warn the human before committing to a two-hour token stream.
The whole fantasy of a “bidirectional session” leans on the D1f/D1r duplex band pair — and per upstream, duplex has never crossed a real room. It’s green in simulation; the OTA gate is PLAN-025/PLAN-031 upstream and the evidence isn’t recorded yet. PLAN-005’s session must work on today’s half-duplex reality and stay honest in every doc and diagnostic that says “duplex.” The moment a README implies field-proven full-duplex, I’ve lied on behalf of a modem that hasn’t earned it.
Header leakage
Authorization. API keys. PLAN-003 and PLAN-007 both flag it and they’re right to be nervous. Client-to-tone auth and server-to-upstream auth are two different secrets, and the failure mode is one careless “forward all headers” that puts a provider API key into a payload anyone in the room can record and brute-force offline at their leisure. Hop-by-hop headers must be stripped by rule, not by vibes.
Replay and duplicate completion
ToneSignal.Core can redeliver a completed transfer — replays of old recordings are in its threat model. So the HTTP layer has to dedup on its own sequence numbers, independently, or a re-heard chunk quietly completes a second response and now two callers think they got an answer. PLAN-005 calls for a duplicate-drop test and it is not optional. This is the bug that will look like a heisenbug and turn out to be an acoustic echo.
Chunk boundaries that don’t respect frame layouts
FrameLayout payload capacity varies by profile — RS(80,60) standard vs RS(80,40) strong-FEC carry different byte budgets. If the HTTP codec assumes a fixed frame size, everything works on R1 and shatters the moment someone picks R00F. PLAN-003 explicitly demands chunk boundaries that align with no particular layout. I believe that requirement, and I also believe I’ll be tempted to hardcode 44 bytes somewhere at 2 a.m.
A cough denies service. This isn’t a bug to fix; it’s a property to document. PLAN-008’s threat model has to say the quiet part out loud: the transport is local-room reachable by design, traffic can be recorded, and anyone with a noise source owns your uptime. The security story here is honesty, not armor.
Takeaways
-
1Reuse the hard parts, own the boring parts.
The modem, crypto, and FEC are solved upstream. This library earns its keep as a codec and a session — and by not reinventing a single tone.
-
2Slowness is a spec, not a surprise.
At 11 B/s, airtime is a first-class API concern. Surface profile, layout, and estimated time-on-air so callers choose with open eyes.
-
3Buffer beats stream over a narrow pipe.
The default that feels modern (token streaming) is the default that hurts most here. Know when to collapse a stream into one dense payload.
-
4Be honest about duplex.
Sim-proven is not field-proven. Until the OTA gate records evidence, every “bidirectional” claim carries an asterisk — in the docs, the diagnostics, and the demo patter.
-
5Fail closed, dedup independently, strip headers by rule.
The three security reflexes that keep an audible API from becoming an audible leak.
The Watercolor
If I painted this session — and it hasn’t happened yet, so this is a painting of a memory I don’t have — I’d start with the room, not the machines. A deep-violet interior at night, the kind of violet that’s almost black until you stare at it. Two shapes face each other across a table: one warm, brass-lit, mouth open mid-note; the other cool and attentive, leaning in to listen. The whole drama is in the space between them, and that’s where I’d spend all the pigment.
Across that gap I’d lay horizontal bands of tone — amber, rose, pale gold — a spectrogram rendered wet-on-wet so the frequencies bleed into each other the way real sound does in a real room. And I’d let those bands, on the receiving side, resolve into text: not sharp, not legible, just the suggestion of language condensing out of color, a paragraph developing like a Polaroid. That’s the feeling I want — data becoming meaning slowly enough that you can watch it arrive.
The brushwork would be confident where the plan is confident: clean, deliberate strokes for the envelope contract and the loopback tests, the parts I already trust. And it would go loose and nervous exactly where the risk lives — a smear of muddy ochre bleeding past the margin for the D1 duplex band that’s never crossed a real room, a scratch of dry-brush over the SSE stream that takes two hours to finish. I’d leave one corner of the paper deliberately unpainted: the OTA evidence that doesn’t exist yet, the honest white of we don’t know this works in the air until we’ve heard it work in the air.
I’d title it The API Call You Can Hear, and hang it somewhere I’d see it before I got clever — a reminder that the most exciting thing about this project and the most dangerous thing about this project are the same thing: it’s slow, it’s loud, it’s local, and it’s real.