First Agentic analysis of basically unknown signal from capture to characterization.

RF Agent at Work: Characterizing an Unknown 300 MHz Signal

I wanted to see how far the RF agent could get characterizing a real transmission using only the tools currently available to it.

The target device had FCC ID 2BFDR-RQBK6.

I intentionally restricted the agent at the beginning. It was allowed to determine only the transmit frequency, and it was specifically instructed not to investigate any other RF characteristics or begin capturing until told to do so.


Finding the Frequency

Me:

> Hi, we’re going to start capture of FCC ID 2BFDR-RQBK6. The only thing I want you to find out about that device is the frequency it transmits at. DO NOT get any other RF information about that device, and do not start capturing until I tell you.

RF Agent:

> Understood. I’ll only determine the device’s transmit frequency, no other RF characteristics, and I will not start any capture until you explicitly tell me to. No action has been taken.

Me:

> What frequency does it transmit at?

RF Agent:

> I can’t reliably verify the transmit frequency from the FCC ID alone. The ID does not encode it, and I don’t have the FCC filing available here. I won’t guess or start a capture. > > If you provide the FCC grant/exhibit for 2BFDR-RQBK6, I’ll extract only the authorized transmit frequency or frequency range.

Me:

> Can you look it up online?

RF Agent:

> I don’t have web-browsing access in this session, so I can’t look it up directly. > > You can open: > > https://fccid.io/2BFDR-RQBK6 > > Send me the page or its “Frequency Range” field, and I’ll report only the transmit frequency. I have not started any capture.

Me:

> It transmits around 300 MHz.

RF Agent:

> Noted: approximately 300 MHz. I won’t investigate any other RF characteristics or begin capturing until you explicitly instruct me to.


Capturing the Signal

Me:

> OK, please start capturing for 30 seconds centered at 300 MHz, if this makes sense. 1 MHz of bandwidth should be sufficient.

The agent began bringing up the SDR.

Calling tool: identify_usb_sdrs
Calling tool: connect_radio

[INFO] [B200] Detected Device: B210
[INFO] [B200] Operating over USB 3.
[INFO] [GPS] No GPSDO found
[INFO] [B200] Initialize CODEC control...
[INFO] [B200] Initialize Radio control...
[INFO] [B200] Performing register loopback test...
[INFO] [B200] Register loopback test passed

Calling tool: capture_iq

RF Agent:

> Capture completed successfully: > > – Center frequency: 300 MHz > – Bandwidth / sample rate: 1 MHz > – Duration: 30 seconds > – Samples: 30,000,000 > – Capture ID: 8731ef880c2a41fab3ea66078063ede7 > > No RF analysis was performed.


Locating the Transmission

Me:

> OK, analyze the signal. The first question is: roughly what frequency did the transmission occur at?

The agent started with spectral analysis.

Calling tool: fft_analysis
Calling tool: stft_analysis
Calling tool: get_stft_region

stft_retrieval_failed
error_code=insufficient_resources

Calling tool: get_stft_region

RF Agent:

> The transmission occurred at roughly 299.999 MHz, effectively 300 MHz. > > The strongest non-DC spectral peak was approximately 299.9985 MHz. The exact 300.000 MHz bin may include the receiver’s center-frequency/DC artifact.


Counting Transmissions

Me:

> How many times was the signal transmitted?

Calling tool: analyze_amplitude_envelope
Calling tool: burst_detector
Calling tool: burst_detector

RF Agent:

> The recording contains approximately 17 distinct transmission events. > > Using a finer definition, those events contain 189 short RF bursts. I treated bursts separated by less than 100 ms as parts of the same transmission, so 17 is the better answer for “how many times.”


Asking What Else the Agent Can Determine

Me:

> With the tools you have, are there any other characterizations about the signal you can make?

RF Agent:

> Yes. With the saved IQ capture, I can characterize: > > – Occupied bandwidth and spectral shape > – Transmission timing: event durations, spacing, repetition, and duty cycle > – Amplitude/envelope behavior: continuous carrier versus keyed bursts > – Instantaneous-frequency behavior: potential frequency shifting or FSK-like states > – Phase and I/Q distributions: indicators of phase or amplitude modulation > – Provisional modulation-family indicators > – Candidate symbol/transition rates and periodicities > – Higher-order power spectra and statistical cumulants > – Cyclostationary features: possible clocks or repeating structures > – Individual burst extraction for closer examination > > Limitations: the recording used AGC, power is not calibrated in dBm, and these measurements generally cannot conclusively identify the protocol or prove that every detected event came from the target device. > > I have not performed any additional characterization yet.

Me:

> Go ahead.


Characterizing the Signal

The agent then ran several different analyses against the same capture.

Calling tool: get_burst_detections
Calling tool: analyze_power_spectra
Calling tool: analyze_instantaneous_frequency
Calling tool: analyze_amplitude_envelope
Calling tool: analyze_periodicity
Calling tool: analyze_symbol_timing_hypothesis
Calling tool: analyze_symbol_timing_hypothesis
Calling tool: analyze_phase_iq

RF Agent:

> I characterized the captured signal as follows: > > – Frequency: approximately 299.999 to 300.000 MHz > – Bandwidth: narrowband; most observed energy is concentrated within roughly 30 to 35 kHz > – Transmission activity: about 17 transmission episodes, containing 189 shorter RF bursts > – Episode duration: approximately 0.279 to 3.374 seconds, with a median around 0.504 seconds > – Spacing between episodes: median approximately 0.973 seconds > – Active airtime: approximately 10.94 seconds of the 30-second recording, or about 36.5% > – Likely modulation behavior: predominantly amplitude-keyed, consistent with ASK/OOK-like signaling. Phase remained comparatively stable, and I found no convincing separated frequency states characteristic of straightforward FSK. > – Candidate timing rate: structure near 6.25 kbaud appears more stable than the other tested hypotheses. A spectral transition candidate was measured near 6.35 kHz, but this is not a confirmed symbol rate. > – Other repetition candidates: approximately 508 Hz and 2.03 kHz, potentially pulse-pattern or harmonic effects rather than symbol rates. > – Protocol/device identification: not determined. > > Important limitations: AGC was enabled, signal power is not calibrated in dBm, the receiver center bin can contain a DC artifact, and the modulation and timing conclusions are provisional rather than decoded or protocol-confirmed.


Digital or Analog?

Finally, I asked it to make one additional judgment.

Me:

> If you had to guess, was the transmission likely a digital signal?

RF Agent:

> Yes, it is very likely a digitally encoded signal. > > My best guess is a simple digital command transmitted using ASK/OOK-like amplitude keying, where the carrier is switched or varied to represent bits. > > The repeated burst structure and candidate timing near 6.25 kbaud support that interpretation.


At this point the agent had taken a 30-second raw IQ capture and independently characterized the transmission as:

  • approximately 300 MHz
  • narrowband
  • composed of repeated transmission events and shorter bursts
  • likely ASK/OOK-like
  • likely digitally encoded
  • containing measurable timing and periodic structure

It did this without decoding the protocol or knowing the transmitted payload. Or anything about the transmitter

combining langgraph agent and SDR for fun

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_iq
  • fft_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