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

The Company I always wanted

I think people misunderstand what I mean when I say I want to build a startup.

They hear startup and think billion dollar empire, venture capital, hustle culture, some guy on LinkedIn saying “we’re changing the world” because he made a dashboard for invoices or whatever.

And sure, getting rich would be nice. I am not going to pretend money is bad. Money solves real problems. Money buys freedom. Money keeps the lights on.

But that was never really the dream.

The dream was way simpler than that.

I wanted enough money to pay the bills, rent an office, buy some food, keep the computers on, and hack with my friends.

That’s it.

A room. Some desks. Some snacks. Good internet. Maybe a whiteboard. Maybe too many monitors. People arguing about systems and programming languages and product ideas. Somebody building something weird in the corner. Somebody else reading tech news for fun. Somebody pushing a deploy. Somebody saying “wait, what if we just…” and then everyone loses two hours chasing the idea because it might actually work.

That’s the part I always wanted.

Not the empire. Not the press release. Not the fake founder mythology. Not the “we are disrupting X” nonsense.

I wanted the workshop.

The funny thing is my friends already do this. They already build things for fun. They already read about technology for fun. They already have opinions about databases and operating systems and AI and networks and whatever else. They already spend their free time doing the thing most companies have to pay people to pretend to care about.

So part of me is like: why couldn’t we build a company out of that?

Not a company where the joy gets crushed under meetings and process and status games. Not a company where snacks replace compensation or where “we’re a family” means “please work nights for free.”

I mean a real company.

One that makes enough money that people can show up and be paid like adults.

One that has customers, and bills, and boring operational stuff handled, but where the center of gravity is still making things.

A company where the work still feels alive.

I think when we were younger this was easier to understand. If somebody made something on a computer, that was cool. A website, a script, a game, a server doing something weird, whatever. The first reaction was not a policy analysis. It was “whoa, you made that?”

Now everything gets filtered through takes.

Is it good for society? Is it cringe? Is it a startup grift? Is it AI slop? Is it capitalism? Is it replacing someone? Is it going to become a monopoly someday?

Some of those are fair questions. But man, if that is always the first reaction, it kills something.

There used to be a basic joy in making the computer do a thing. I still have that. I do not think I ever lost it.

And I guess I am realizing that the company I always wanted was not really about winning capitalism. It was about making a place where that joy could survive adulthood.

Because adulthood changes things. People have partners, kids, mortgages, health problems, parents getting older, responsibilities. Sitting in a room with friends, free food, and computers does not sound as magical to everyone as it once did. To some people it sounds like one more obligation.

I get that.

So maybe the dream has to grow up too.

Not a hacker house. Not a grind cave. Not “sleep under your desk until we exit.”

More like a sane little lab.

A software shop with a soul.

A place where people can come in, do good work, make real things, get paid, eat lunch, laugh, argue about dumb technical details, and then go home without feeling like the company owns them.

That sounds modest compared to the billion dollar startup dream, but to me it feels bigger in the ways that matter.

Because the point was never just money.

The point was: can we make our natural behavior economically sustainable?

Can the thing we already love doing become the thing that pays for the room?

Can we build something useful enough that it buys us more time to build more things?

That is the company I always wanted.

Not a unicorn.

A workshop with revenue.

A place where the fridge is full, the servers are running, the work is interesting, and the people in the room still think making stuff on computers is cool.

All your CDN are belong to US

Underminr Detection: DNS Says One Thing, TLS Says Another

The detection story for Underminr is actually pretty simple, which is why it is so annoying.

You are not looking for some magic evil packet. You are looking for the layers of the connection telling different stories.

Normal web traffic looks something like this:

DNS: I want goodsite.com
DNS answer: goodsite.com is at 104.x.x.x
TLS: I am connecting to 104.x.x.x
SNI: goodsite.com
HTTP Host: goodsite.com

Everything lines up. Boring. Fine.

Underminr-style traffic looks more like this:

DNS: I want goodsite.com
DNS answer: goodsite.com is at 104.x.x.x
TLS: I am connecting to 104.x.x.x
SNI: sketchy-domain.ai
HTTP Host: sketchy-domain.ai

That is the trick.

The machine uses an allowed domain to get a perfectly valid CDN IP, then reuses that IP to talk to some other tenant living behind the same CDN.

DNSSEC does not really save you here, because the DNS answer can be totally legitimate. DNSSEC can prove that the DNS answer was authentic. Great. Congratulations. The lie happens later, at the TLS / HTTP / CDN routing layer.

So the detector has to correlate things across layers:

What DNS name did this endpoint ask for?
What IP did DNS return?
What IP did the endpoint connect to?
What SNI did it present?
What HTTP Host header did it use?
Did that hostname ever get resolved normally?

In other words, you are looking for this mismatch:

Resolved: harmless-site.com -> CDN edge IP
Connected to: CDN edge IP
Claimed SNI: evil-site.com

That is the “oh come on” moment.

The really cursed version is when the attacker splits it into steps. First they make a clean-looking connection to the allowed domain, then they come back to the same CDN edge IP and swap the SNI / Host header to the real target.

So detection has to remember recent DNS answers and recent CDN connections per machine. It is not enough to look at one packet in isolation.

The direct-to-IP version is even more annoying because there may be no DNS lookup for the bad domain at all. Then the question becomes:

Why is this machine connecting directly to a shared CDN IP
while presenting an SNI / Host name that it never resolved?

That is suspicious as hell.

And with ECH, because of course we needed one more layer of pain, the real SNI can be encrypted. At that point you need endpoint visibility, controlled DNS, policy around HTTPS / SVCB records, or some way to block or strip ECH in managed environments.

So the whole thing boils down to this:

DNS says one thing. TLS / HTTP says another. The CDN accepts both because shared edge infrastructure is a haunted house.

Honestly, it feels like Dan Kaminsky may have faked his own death and come back on the dark side.

Obviously joking. Mostly.

But this is exactly the kind of DNS-adjacent, “everything technically works as designed and that is the problem” nonsense that makes the internet beautiful and horrible at the same time.

The practical detection rule is basically:

For each endpoint:
remember recent DNS answers:
allowed-domain.com -> CDN_IP
watch outbound TLS / HTTP:
endpoint -> CDN_IP:443
SNI / Host = some-other-domain.com
alert if:
the CDN IP came from a recent allowed DNS answer
but the SNI / Host does not match that DNS name
and the SNI / Host name was not separately resolved
in a normal allowed way

That is Underminr detection.

Not “block the IP,” because the IP is probably Cloudflare, Akamai, Fastly, or some other giant shared CDN edge.

Not “DNSSEC fixes it,” because DNSSEC only proves the DNS answer was real.

The actual problem is that the trust decision was made on one name, and the connection was later used for another name.

It is a cross-layer trust bug.

The internet has a lot of those.

Because apparently we enjoy pain.

Thoughts on the Mistakes of the Social Web

The internet was social before the social web. That part gets forgotten. People talked on IRC, forums, mailing lists, Usenet, AIM, Discord-style rooms, and all kinds of weird little places. The difference was that those places had context. You were not just “an account.” You were a person in a room, with some history, some reputation, and some reason for being there.

If you linked to your own thing in one of those spaces, people usually did not lose their minds as long as it was relevant. The question was not, “Did you make this?” The question was, “Is this useful here?” That is a much healthier standard. Sometimes the best link in the conversation is your own link because you are the person who wrote the thing, built the thing, documented the thing, or found the thing.

The social web broke that in a strange way. It created a huge opening for credibility fraud. Suddenly everyone could perform expertise, manufacture popularity, juice engagement, buy followers, write in brand voice, growth-hack sincerity, and pretend to be a participant while actually acting like a little attention-extraction machine. The feed turned normal human sharing into a suspicious transaction.

So now we live in this dumb world where links are both the blood vessels of the web and somehow treated like contraband. A web with no links is barely the web. It is just a set of private malls with recommendation engines and security guards. “Do not link to your own stuff” sounds noble until you realize it mostly helps people who are already big enough that other people link to them automatically.

PageRank made sense in a world where someone else might find your weird little page and link to it from their weird little page. That was the old bargain: publish something good, and the graph of the web slowly discovers it. But a lot of that middle layer is gone or weakened. Personal sites, blogrolls, directories, small forums, and independent linking culture got paved over by platforms. Now the system still wants backlinks, but the places where people actually gather often punish the behavior needed to create them.

That is one of the great little ironies of the modern web. The machine wants evidence that the world cares, but the world has been trained to treat public linking as spam unless it comes from someone already blessed by the machine. Nice little closed loop there. Very elegant. Completely cursed.

There is an important exception here: I am much less hostile to things like Bluesky, Mastodon, ActivityPub, AT Protocol, and other systems that at least try to make the social layer protocol-shaped instead of purely platform-shaped. That matters. Federation is not magic fairy dust, and protocol people can still be annoying in the very special way protocol people are annoying, but the architecture is pointed in a better direction.

A federated or protocol-based social system is not the same animal as a giant closed platform casino. If identity, distribution, clients, moderation, and hosting can be separated, then users are not trapped in quite the same way. The conversation can move. The client can change. The server can change. Communities can set local norms. The graph is not just locked in some corporate basement next to the engagement-optimization goblin.

That does not make every federated system good. It does not mean Bsky or Mastodon or anything else automatically solves the human problems. People can bring status games, mobs, spam, and weird little dominance rituals anywhere. Give humans a protocol and we will eventually find a way to argue about the chairs. But protocol-based social is at least trying to preserve some of what made the internet good: links, portability, interoperability, local context, and the possibility that no single company gets to be the landlord of human conversation.

So the problem is not “people talking online.” That would be an insane take. The internet is one of the best machines humans ever made for finding each other. The problem is the platform-owned social web: permanent, indexed, engagement-maximized, reputation-scored, and monetized within an inch of its life.

I understand why everyone built social features. In the pre-AI era, if you wanted a big site, users were the cheapest way to get content. Users wrote the posts, uploaded the photos, made the comments, tagged the pages, reviewed the restaurants, liked the posts, ranked the content, argued with each other, moderated each other, and generated the graph. The whole thing rode on the backs of users because paying people to produce and organize all that stuff was expensive as hell.

But that bargain had a cost. The user did not just contribute to the product. The user became the product, the inventory, the moderation problem, the credibility signal, and eventually the unpaid little hamster powering the engagement wheel.

And then because everything was public, permanent, indexed, and monetized, normal social behavior got weird. A casual thought became content. A disagreement became a searchable artifact. A joke became evidence. A person became a profile. A community became a growth channel. Human interaction got shrink-wrapped, barcoded, and stacked on a pallet in the warehouse of the feed.

I do think the social web has value. I am not saying people should stop talking online. But social interaction is often temporary, contextual, and messy. The web is durable, searchable, and decontextualized. Those are not naturally the same thing.

That mismatch is where a lot of the damage came from. We took ephemeral human behavior and made it permanent infrastructure. We took conversations that should have lived in rooms and put them on billboards. Then we acted surprised when everyone got performative, defensive, spammy, paranoid, or insane.

Maybe the healthier split is simple: let the web be good at durable reference, and let social be good at human context. Links, pages, sources, guides, documents, indexes — those belong on the web. Jokes, arguments, half-formed thoughts, “you had to be there” moments, and random social chatter probably belong somewhere smaller, softer, more local, or at least more portable than the giant engagement platforms.

AI changes the economics here. It may now be possible to build useful information systems without forcing users to generate the entire content layer. Machines can parse public sources, organize messy information, summarize, classify, dedupe, and turn scattered material into something usable. Humans can review and steer instead of being mined for every post, like, comment, and scrap of attention.

That does not mean AI slop should replace human culture. Please, God, no. The last thing we need is the web turning into a haunted vending machine full of synthetic LinkedIn posts. But it does mean we may not need to make every useful site into a little social casino anymore.

The mistake of the social web was not that people talked to each other. Talking is good. The mistake was turning talk into permanent content, content into ranking fuel, ranking into status, status into credibility, and credibility into a fraud market.

The web should have links. People should be allowed to point at things. Making something useful and saying “here, I made this” should not automatically be treated like some moral failure. That is how the web breathes.

A link-hostile web is an anti-web. It is a graph afraid of its own edges.

But a protocol-shaped social web? A federated web? A web where people can talk without every conversation becoming feed chum for the same giant machines?

That might be worth saving.

A Love Letter to San Leandro

San Leandro is the kind of city that reveals itself slowly. https://sanleandrodaily.com

It does not usually announce itself with the same volume as some of its neighbors. It does not have to. Its character lives in smaller, steadier things: a public library that matters, neighborhoods that still feel like neighborhoods, local restaurants people genuinely return to, parks that get used, and a shoreline that can change the tone of an entire day.

That quiet substance is part of why I keep coming back to it.

San Leandro feels practical in the best sense of the word. It feels lived in. It feels useful. It is a place where civic infrastructure still matters, where community programming is not an afterthought, and where small businesses still help define the texture of daily life. There is a kind of dignity in that. A city does not need to perform for the outside world to be worth loving.

From a technical point of view, San Leandro is more interesting than people sometimes realize. It has real industrial history, a meaningful business base, and even its own unusual infrastructure story through Lit San Leandro and the city’s long-running interest in connectivity and modern economic development. That combination of public life, local identity, and technical ambition is rare. It is one of the reasons building software for this city feels so worthwhile.

That is also the spirit behind San Leandro Daily.

The goal is not to build a generic city app and stamp a local name on it. The goal is to build something that respects the actual rhythms of San Leandro: library events, public meetings, neighborhood happenings, family programs, local deals, and the many small signals that tell you a city is alive if you are paying attention. Under the hood, the work is fairly straightforward on purpose: a FastAPI backend, PostgreSQL for content, and a cross-platform mobile stack that keeps the product maintainable. The technology matters, but only because it helps the city show up more clearly.

That is the heart of it for me. San Leandro deserves clear attention. It deserves tools that make it easier to see what is already here.

Some cities demand to be noticed. San Leandro rewards noticing.

Please send good vibes positive thoughts and prayers to my friend Desta in Kenya

Desta and I have been close friends for a few years now. Unfortunately, she is currently very sick with lung cancer. The world can be a very unfair place. She graduated #1 in her nursing school class with near-perfect test scores, all while struggling through abject poverty. In the middle of her studies, her mother died, and she took care of — and continues to take care of — her two younger siblings.

I know she will not give up, but she could certainly use all the positive thoughts she can get. So I am asking all my readers around the world to take one small moment and mentally wish her well.

Thank you all.

LessVibes release

fuck the claw

lessvibes

lessvibes is an early JetBrains plugin project aimed at a pretty specific problem in the AI coding era: code can appear in your project faster than you can really read it.

The point of lessvibes is not to block AI tools or shame anyone for using them. The point is to make AI-assisted coding more visible and more hands-on. The plugin is meant to notice when a burst of code lands, track whether the affected files were actually opened, and help the developer step through a likely code path instead of just trusting the vibes and moving on.

In its current form, the project is focused on PyCharm first. The rough direction is:

  • track likely assisted or bulk-generated changes
  • show which files were touched
  • show which of those files were never opened
  • show which files were opened but never really edited
  • give the user a way to open a bounded left-to-right code-flow view

The project lives here:

https://github.com/nigeldaniels/lessvibes

Installing It In PyCharm

Right now, the simplest way to install lessvibes is from a locally built plugin zip.

  1. Clone the repo.
  2. From the project root, run:
./gradlew buildPlugin
  1. That produces a plugin archive at:
build/distributions/lessvibes-0.1.0.zip
  1. In PyCharm, open:
Settings / Preferences -> Plugins -> gear icon -> Install Plugin from Disk...
  1. Select the generated zip file.
  2. Restart the IDE if PyCharm asks.

After restart, the plugin should appear as the lessvibes tool window.

Installing It In Similar JetBrains IDEs

Because lessvibes is a JetBrains plugin, it may also load in similar IntelliJ-platform IDEs. That said, this project is being built with PyCharm in mind first, so anything outside PyCharm should be treated as experimental for now.

The install flow is basically the same:

  1. Build the plugin zip with ./gradlew buildPlugin
  2. Open the target JetBrains IDE
  3. Use Install Plugin from Disk...
  4. Pick the generated zip
  5. Restart the IDE

Important Warning

This project is still very much a work in progress.

It is mostly untested, the heuristics are still rough, and the code-flow logic is best-effort rather than guaranteed runtime truth. If you try it today, you should expect edges, gaps, and wrong guesses.

That said, the idea is real, the first plugin scaffold exists, and contributions are absolutely welcome.

If this problem sounds interesting to you, open an issue, send a PR, or just poke around the code here:

https://github.com/nigeldaniels/lessvibes