
I Built an Agent That Can Capture and Analyze Radio Spectrum
I have been experimenting with a fairly simple idea: what happens if you give an AI agent the same basic radio tools I normally use manually?
The result is an early LangGraph-based SDR agent written in Python.
Right now the radio interface is just the available Python wrapper around the USB driver. The agent itself is running GPT-5.6 and currently has only two real RF tools:
capture_iqfft_analysis
The FFT analysis is implemented with PyFFT.
That is basically it.
My goal was not to build some giant SDR framework. I wanted to start by giving the model a few of the same primitive tools I already understand from GNU Radio and see what it could do with them.
Capturing the FM band
I gave the agent a pretty casual instruction:
Let’s try a 30 second capture centered at the middle of the FM dial at 20 MS/s worth of bandwidth.
The agent translated that into:
- Center frequency: 98 MHz
- Sample rate: 20 MS/s
- Bandwidth: 20 MHz
- Duration: 30 seconds
- Gain: 30 dB
- Samples: 600,000,000
- Data size: about 4.8 GB
- Format: SigMF,
cf32_le
It captured the data and stored both the SigMF data and metadata files.
Nothing particularly magical there. It is just calling the capture tool.
The interesting part came next.
“Examine the captured data”
My next instruction was:
Examine the captured data and tell me at what frequencies the most prominent peaks are.
The agent already knew which capture I meant from the conversation context.
It ran a 4096-point FFT over the capture and reported 23 detected peaks, along with an estimated noise floor.
The ten strongest peaks it found were:
| Rank | Frequency | Above noise |
|---|---|---|
| 1 | 88.498 MHz | 35.0 dB |
| 2 | 94.899 MHz | 19.3 dB |
| 3 | 95.700 MHz | 17.7 dB |
| 4 | 99.699 MHz | 17.2 dB |
| 5 | 106.901 MHz | 16.1 dB |
| 6 | 91.101 MHz | 15.3 dB |
| 7 | 103.708 MHz | 14.9 dB |
| 8 | 96.501 MHz | 14.6 dB |
| 9 | 104.499 MHz | 14.6 dB |
| 10 | 106.101 MHz | 13.5 dB |
The FFT tool also writes a JSON analysis report, so the result becomes another piece of data the agent can refer back to later.
Identifying the stations
Then I asked:
Remake that list and identify which are known stations in Oakland, California.
The agent matched the measured peaks to Bay Area FM assignments:
| Measured peak | FM channel | Likely station |
|---|---|---|
| 88.498 MHz | 88.5 | KQED-FM |
| 94.899 MHz | 94.9 | KYLD |
| 95.700 MHz | 95.7 | KGMZ |
| 99.699 MHz | 99.7 | KMVQ-FM |
| 106.901 MHz | 106.9 | KCBS-FM |
| 91.101 MHz | 91.1 | KCSM |
| 103.708 MHz | 103.7 | KOSF |
| 96.501 MHz | 96.5 | KOIT |
| 104.499 MHz | 104.5 | KNBR-FM |
| 106.101 MHz | 106.1 | KMEL |
Importantly, it also made the distinction that these were frequency-assignment matches, not confirmed station IDs decoded from the RF signal.
That is the correct distinction to make.
The TL;DR
I basically made a chatbot that you can tell to capture and analyze radio shit.
In this case it captured roughly 20 MHz of spectrum containing ten major FM stations simultaneously, ran an FFT against the recorded IQ, extracted the major peaks, generated a report, and then used the results later in the conversation.
That last part is probably the most interesting thing to me.
The capture is no longer just a file sitting on disk.
The agent knows:
- what it captured
- where the capture is stored
- what analysis has already been performed
- what peaks were detected
- what additional questions I am asking about those results
So instead of manually moving between an SDR application, files, FFT scripts, Google searches, and ChatGPT, I can just continue the conversation.
Why this feels better than my normal SDR workflow
Normally I might be sitting around asking ChatGPT how to configure GNU Radio, then clicking and dragging blocks around in GNU Radio Companion, running something, looking at the output, copying values somewhere else, and then coming back to ChatGPT.
This is much easier.
The interaction becomes:
Capture this part of the spectrum.
Then:
What is in it?
Then:
What are those signals likely to be?
That is much closer to how I actually want to interact with radio tools.
To be clear, the AI is not replacing the DSP.
The FFT is still an FFT.
The SDR driver still controls the radio.
The interesting part is that the language model can decide when and how to use those capabilities and preserve the results as part of an ongoing investigation.
Why I am not giving it GNU Radio yet
Someone has already built a GNU Radio MCP server, which is obviously tempting.
I am deliberately holding off.
I want to understand what the smallest useful set of radio primitives looks like before I hand the agent an entire GNU Radio environment.
Right now I have:
capture_iq
fft_analysis