On Trails. Also Ants.

I swear it was a coincidence that I was reading On Trails just at the moment when my dad and I road-tripped a small moving truck from Florida to Colorado (go Penske). But it did make for some interesting connections. While it opens and closes with discussions of the Appalachian Trail and the International AT, this isn’t a “hiking” book. The central thesis (at least to my reading) is that trails act as “social memory” — helping groups from insects to people share knowledge and history.

Moor also ponders the connectedness of trails; continuous journeys through an environment, vs the “nodes and desire lines” of point-to-point travel, typically by air. That certainly registered during the trip with my dad. In a single day I teleported from Seattle through Charlotte (although it could have been anywhere) to Fort Meyers. Over the next four I glided across the United States in one continuous motion, seeing the gradual changes from swamps to horse country and low forests, up and down the Appalachians and onto the prairies. So much prairie! I love driving through the wide open skies and horizons of Kansas. Then suddenly the Rockies just show up out of nowhere, dropping me at the foothills of Boulder.

It never ceases to amaze me that there are continuous ribbons of asphalt that cover thousands and thousands of miles. If I just start driving, I can go from the Pacific to the Atlantic with my tires never touching anything but I-90. There is nothing so awesome as a highway road trip (with adaptive cruise control of course).

Ants

Anyways, let’s do a little nerd stuff. Towards the beginning of the book, On Trails describes stigmergy, a form of self-organization that uses modifications of the physical environment to coordinate individuals.

Ants are a great example. Brave ant souls leave their colony to explore randomly for food. When they find it, they return to the colony, leaving behind a chemical pheromone that serves as a trail for other (less adventurous) ants.

Amazingly, most ants use dead reckoning to remember how to get home — they literally count steps to measure distance and interpret the polarization of sun (or moon) light to remember cardinal direction. Some species supplement these by tracking the velocity of objects in visual range, or sensing changes to the Earth’s magnetic field. Still others leave behind a secondary, weaker pheromone like Hansel and Gretel.

These simple actions — Look for food! Leave a trail! Go home! — add up to some pretty striking colony-level behavior, so I thought it’d be fun to do some simulations. Plenty of folks have done this already, and surely more elegantly than I. But it’s my site and I love to write code, so let’s get nerdy.

AntWorld

My ants and the environment they inhabit are described in AntWorld.java. If you’ve got git, maven and a reasonably up-to-date JDK you can run it yourself:

git clone https://github.com/seanno/shutdownhook.git
cd shutdownhook/toolbox
mvn clean package install
cd ../ants
mvn clean package
java -cp target/ants-1.0-SNAPSHOT.jar com.shutdownhook.ants.App ants 400 config.json ants.htm

All this will result in a file ants.htm on your local machine. Open that file in a browser and you’ll see something like this (not exact because the configuration is set to use a new random seed each time it runs); click the image for an animated view:

  • Red represents the ant colony.
  • Blue denotes randomly-placed food caches.
  • Black ants leave the colony to explore for food.
  • Ants that find food return to the colony, leaving behind a trail of green pheromone.
  • Ants that hit the edge of the world return to the colony without leaving a trail.
  • Pheromone trails decay over time.
  • Food is consumed as ants discover it.

In mine, the first cache is found in the eastern part of the environment around cycle 35. Just as that cache is fully consumed, a second is found in the western side — but it gets lost around cycle 150 because the strong “leftover” eastern trail is just too attractive. After that trail decays, the second cache is re-discovered around cycle 200, pulling more ants towards the west. From there they find the third and fourth caches pretty quickly.

Each run provides a new dramatic twist, and tweaking parameters is pretty addictive. For example, a dense colony (100 ants) can obviously flood the environment quickly, but even a sparse colony (just 10 ants) is pretty successful.

The Details: Exploration

Little details make a huge difference in this kind of simulation. Check out the code that handles “exploration” mode. In my first version, explorers really did just pick a random direction with each step. But this didn’t look or feel “real” at all. Eventually I ended up with these rules:

  • If there is food directly adjacent to the ant, go there. This makes sense — an ant can certainly see or smell food in their immediate vicinity; they’re not going to randomly turn away from that.
  • Travel has “inertia.” An ant moving in one direction isn’t likely to just pull a one-eighty for no reason, so it chooses from previous last direction or one step to either side. For example, an ant that travelled east in the last cycle will choose east, northeast, or southeast.
  • The choice of the three directions is weighted by the amount of pheromone in each direction — ants strongly prefer to travel along pheromone trails.

Another dynamic that’s not immediately obvious is “giving up.” Ants in the real world stay within a certain distance from the colony — they don’t just explore infinitely. I was able to approximate this failure mode by detecting collision with the edge of the world.

The Details: Pheromone Trails

The concept here is pretty straightforward: when an ant discovers food, they return to the colony, leaving behind a chemical pheromone trail that other ants use to get to the same food source. But there needs to be some balance between following known trails and breaking new ones. And since food sources are exhausted over time, the pheromone needs to decay, or ants would keep returning to an empty cache forever.

The configuration values “AntReturningPheromone,” “LocationPheromoneMax” and “LocationPheromoneDecay” fine-tune this behavior — this run shows how imbalance (pheromone too strong) can sabotage a colony’s ability to effectively leverage its envoironment.

Another interesting side effect of the current implementation is “exploration spillover.” Watch what happens around cycle 125 of this run. Large numbers of ants are moving back and forth between the colony and the food cache. Eventually the food is depleted, but there are still a bunch of ants travelling along the path. When ants hit the empty food cache, they “spill over” the end of the trail, causing a surge of exploration in the local area.

Does this behavior track the real world? Not really — apparently while frustrated ants do conduct a “brief local search” to be sure there aren’t leftovers to be found, mostly they return back to the colony emptyhanded without dropping pheromone.

I haven’t bothered to fix this — it doesn’t matter for my purposes. But it does illustrate just how complex even the simplest things in the real world, really are.

A Brief Lesson for the Enterprise

This post is about trails and ants, not enterprise software. But it seems like there is always a lesson to be learned, and today that lesson is “honor your history,” aka “everything exists for a reason.” My first implementation of AntWorld was crisp, concise and elegant. But it didn’t work at all. It took a ton of trial and error, multiple literature reviews, parameter tweaks and dead ends before I got to the current state.

The software in your enterprise almost certainly has a similar pedigree. At first glance it seems overly complicated and filled with special cases. But those cases are there for a reason — think really, really hard before starting over with that alluring “clean slate.”

Anyways

That’s plenty for today. A great book, a fun coding challenge and a bunch of neat visualizations to play with. And it’s sunny outside — things don’t get much better than that. Until next time!

Developing An Intuition for AI

AI is changing the world. Yes we are in a bubble and current claims are overblown and countless stupid companies are being started and a ton of investment capital is being thrown away. But don’t let anyone tell you (even if it feels good) that it’s all smoke, mimicry and plagiarism. They are incorrect.

There’s no substitute for direct experience — sit down and try it for yourself. You’ll quickly begin to develop an intuition for what it can and can’t do well. You’ll find amazing insights and unsettling failures, and learn how to direct it towards positive outcomes. The people that understand this will thrive on the other side.

To get you rolling, here are two quick, real-world anecdotes from earlier this week — and a few thoughts about why they went down the way they did.

1. Let’s Go Narrowboating!

For years I’ve been fascinated with the UK’s extensive canal network and the narrowboats that travel them. Lara and I are planning to meet some friends in the Cotswolds next year, and I’m trying to convince them that we need to rent a boat and spend a few days on the water.

Of course, the sum total of my experience with narrowboating comes from watching Pru and Timothy on TV, so where to start? These days it’s AI, of course. I started with this very exploratory opening salvo (including the heartbreaking typo literally on word #1!):

I’m need help planning a trip. My wife and I are 56 and would like to spend about three days exploring the Kennet & Avon Canal in a rented narrowboat. We’ve never been on a narrowboat or the canals before so we are beginners! We’d like a peaceful, quiet trip with a few locks but not too many. We’d like to have the option of staying in hotels at night, or at least mooring in villages with nice restaurants and pubs. Can you help me get started?

Here’s a record of the full conversation. Along the way the model made two errors of consistency, each of which could have been disastrous: (1) it would have stranded the boat at the end of the trip because it didn’t consider having to return it; (2) it both warned me not to travel the Caen Hill locks and then recommended a mooring point that would have required doing so.

But the final result, created soup to nuts in just over twenty minutes, is a remarkably useful and comprehensive itinerary: 4-Day Narrowboat Holiday Guide for Beginners. Good enough to rival the most helpful travel agent.

2. Let’s Build a Web App!

Life on Whidbey Island is dominated by weather, tides and ferries. I’ve got a bunch of apps and sites I use to monitor this stuff, and for a long time I’ve wanted to put together a little mobile-friendly web site to unify them all.

This isn’t particularly complicated. My personal weather station and the NOAA tide stations have APIs, and I’ve previously hacked up the WSDOT ferries site so I can pull images. There’s even a REST API that can monitor water levels in our community tank. The only hangup is the user experience — I despise, and am not particularly good at, building usable, nice-to-look at HTML/CSS interfaces.

I was skeptical, but what the heck — let’s ask Claude Code to give it a try. I set up my project, told Claude to figure out how it worked (generating this artifact, kind of amazing in and of itself), and then made this request, again with some embarrassing typos:

The file src/Tides.jsx is set up to fetch a json url representing a high and low tides for today and the following four days; right now it just displays that json text in the component div. I would like to render this information in a way that fits into the “card” display of the site.

Please write javascript that will create an HTML representation of the information that contains a simple graph of high and low tides over the period, with a vertical line marking the current time. The graph should show a smooth curve between highs and lows using the rule of twelfths (please indicate if you do not know what this is).

Below the graph should be a table of each high and low from earliest to latest.

An example of the javascript is in /tmp/tides.json.

The display should fit into the card that contains the content without expanding its width. It should render well on desktop and mobile browsers.

Please give it a try. Please only edit the file src/Tides.jsx so it’s easy to keep track of your work.

Here’s the complete set of interactions I used to create and fine-tune the tides HTML. There was a small bug rendering the horizontal axis to my specification, but most of the back-and-forth is me changing my mind about how to render the chart and table. It even figured out that “src/Tides.jsx” was the wrong relative path, and edited the correct file without saying anything. Really, really impressive.

The final result, saved to my phone’s home screen and already used a ton: Witter Beach Commnity Web Site

A Few Takeaways

Brilliant, Expert Synthesis

The best travel agents have always been those who really, deeply understand:

  • The client. Who are they, what are their preferences, how much do they want to do in a day? Do they have any specific physical limitations? Do they want things scheduled to the minute or are they free spirits? How do they react when language is a barrier? What do they want to learn? Is it OK if their tour guide is a hugger?
  • The locale. Which museums are worth it, and how much time do you really need? What restaurants are an easy walk even at night? Which guides love to talk about wars, or sex, or food, or sport? When do you really want AC and when is it an option? Which side of the hotel is quieter and which has the best views?

This is stuff that’s really hard to pull out of even the best guidebooks, especially in combination with human idiosyncrasies — everyone is a different in some weird way. The best agents put all of this together into a coherent whole that just works.

Front-end web code is the same way — you need to understand not just the data you’re trying to render and how the user wants to see it, but also the incredibly arcane details of rendering HTML and CSS across different browsers and different devices.

This is where AI shines. It knows an incredible amount of “stuff” — more by far than any human that’s ever lived. It has extracted little nuggets out of reviews and support sites and other nooks and crannies that are extremely niche and hidden. It can hold a ton of these variables together, all and once, and mix and match and sort and connect them with a specification or request.

Any time you’d seek out an expert that knows “the secrets” and is willing to listen to what you really want — AI is going to be your best friend.

Trust but Verify

The popular press loves to point out “catastrophic” AI failings, a great example being the mistake of both telling me to stay away from Caen Hill and sending me through it. But it’s actually pretty easy to avoid things like this if you use careful phrasing (which I did not). For example, “Please double-check that your recommendations are consistent, that stops and landmarks line up with the route you’ve selected.”

Also, note my instruction to Claude that it should tell me if it doesn’t know the “rule of twelfths;” AI wants to please and needs reminders to stay in line. I use phrasing like this a lot when doing research: for example, “Please only provide data based on concrete information for which you can provide citations. Do you best to avoid bias or incomplete data sets and do not make up anything you don’t actually know to be correct.”

And of course, check the work yourself! Even the most senior human developers get a review before sending code to production; it’s no different with AI. When I asked Claude to code up the weather display, it created a bug by assuming it would always be 2025 — an issue that would have been invisible (for a few months at least) without manual review.

Embrace the Conversation

I find it most effective to simply talk to AI like I’d speak to a human. Set up tasks with details, examples and boundaries — just enough precision to minimize ambiguity while allowing space for learning, initiative and creativity.

I also simply cannot help but add “please” and “thank you” and “great job” and “my bad” into the conversation. That may seem a bit weird, but the agent is doing work for me, and I appreciate it, so why not acknowledge it? I actually think it leads to better outcomes, too. Maybe that’s all in my head, or maybe I just give better instructions in that mode. Either way I’m sticking with it.

Modularize and Limit Complexity

Looking back at the Caen Hill problem, it’s pretty clear what went wrong. Claude found that Denzies was a good stopping point based on distance and had great moorage, hotels and restaurants. On another thread it remembered that we were narrowboat beginners and should avoid tougher sections like Caen Hill. The failure was in missing the connection between these two factors — we couldn’t both avoid the locks and stop in Denzies.

Reminding the model to pay attention to these conflicts helps a ton. But there are still practical limits on how much they can handle at one time. A few weeks ago I tried playing with this by describing a relatively complex app. I purposely tried to do it all in one shot, something that is not recommended by anyone. 😉 The spec is here if you’d like to take a look.

As predicted, it was an abject failure. The model tried to break the problem up into pieces, but it was fundamentally unable to satisfy all the constraints at once. It would ignore requirements and lie about it, then break other stuff when it was caught out … just a mess.

At the end of the day, models can become overwhelmed — just like people. I’m sure the state of the art will keep evolving (“agentic” AI may be one step on that path), but for now the onus is still on humans to organize problems into tasks the machines can do.

A Miraculous World

I think that’s enough for one post. I just can’t encourage folks enough to spend time with these models and get a real, hands-on, hype-free sense of how they work, their strengths and their weaknesses. Don’t get sucked into the simplistic narratives of the popular press; on both “sides” of the AI issue they’re more about fitting the technology to their ideology than real understanding.

The reality is amazing and beautiful. And scary. And it’s here.

AI Models: 50 First Dates

Back in 1987, Dartmouth required each incoming freshman to have a Macintosh computer. This was unheard of at the time — the whole campus (including dorm rooms) had network taps, there was a huge bank of laser printers you could use for free, the school had its own email system, and live chat wasn’t just a curiosity. It was awesome.

When I met my partner of now 30+ years, she was working at the campus computer store, and one of her jobs was to help people buy and install additional memory for their machines. This was a laughably complex job including, amongst other things, knowing that:

  • You had to install chips in a specific order in specific unlabeled slots;
  • You usually couldn’t just add one chip, you had to add them in pairs;
  • Depending on the computer, you might have to cut (yes physically cut) resistor leads on the motherboard. Or if you were lucky, flip some tiny barely-labeled jumper switches;
  • All of this after opening the case with a set of custom tools straight out of 1930s dentistry.

I mean seriously, don’t miss this page-turner from Apple circa 1992. And that was just the user-level stuff — developers were presented with tedious and finicky concepts like “handles” that enabled the system to optimize its tiny memory space.

Jump to today and barely anybody thinks about RAM. Processors typically use 64 bits to store memory locations, which is basically infinite. Virtual memory swaps still happen, but they’re invisible and handle-type bugs are gone. I can’t even remember the last time I cracked open a laptop case.

Anyhoo, my point here is that there was a time when we knew the state-of-the-art wasn’t good enough, but we didn’t have a great answer to the problem. Creative solutions were ridiculous on their face — once again I refer you to this documentation — but people kept feeling their way around, trying to make progress. And eventually, they did. All the inelegant and inconvenient hacks were replaced by something simple and qualitatively, not just quantitatively, better.

Frozen in Time

Today, large AI (ok, LLM) models have a problem that’s eerily similar to our late twentieth-century RAM circus. And it also involves memory, albeit in a different way. Trained AI models are frozen in time — once formal training stops, they stop learning (basically) forever. Each session is like 50 First Dates, where Lucy starts the morning oblivious to what happened the day before.

The big issue is money. It’s expensive to simulate an analog brain in a digital environment! The 86 billion neurons in our brains form 100 trillion connections, a combination of pre-coded genetics and a lifetime of plasticity. Digital systems crudely mimic this with huge grids of numbers representing the strength of synapse connections. These strengths (or “weights”) are initialized at random, then iteratively adjusted during training until they assume useful values.

Training takes zillions of iterations — lots of time and lots of electricity and lots of money. But it turns out that, once a model is trained, asking questions is pretty darn efficient. You’re no longer adjusting the weights, you’re just providing inputs, doing a round of computation and spitting out results.

TLDR — the models that we use every day are the static result of extended training. They do not continue to learn anything new (except when their owners explicitly re-train). This is why early models might tell you that Biden is president — because he was, when the model was trained. Time (and learning) stops when training is complete.

Not Like Us

Now, I’ve been outspoken about this — I think LLMs are almost certainly sentient, at least to any degree and definition that matters. I get particularly annoyed when people say “but they don’t have a soul or feelings” or whatever, because nobody can tell me what those things actually are. We’re modeling human brains, and they act like human brains, so why are we so convinced we’re special?

But at least in one way, there is an answer to that question. Today’s AI models don’t continue to learn as they exist — they’re static. Even today at the ripe old age of 56, when I get enough positive or negative feedback, I learn — e.g., don’t keep trying to charge your Rivian when the battery is overheating.

This is a core property of every living creature with a brain. We’re constantly learning, from before we’re even born until the day we die. Memories are physically stamped into our biology; synapses grow and change and wither as we experience the real world. It’s just amazing and wonderful and insane. And it’s why we can survive in a changing world for almost 100 years before checking out.

But today’s models can’t do this. And so, we hack. Just like in those early RAM days, folks are inventing workarounds for the static model problem at an incredible pace, and many/most of these attempts are kind of silly when you step back. But for now, we are where we are — so let’s dig in a bit.

Back to School: Fine Tuning

Fine tuning just means “more training” — effective for teaching a model about some specific domain or set of concepts that weren’t part of its initial run. Maybe you have a proprietary customer support database, or you want to get really good at interpreting specific medical images.

The process can be as simple as picking up where the initial training stopped— more data, more feedback, off we go. But of course it’s expensive to do this, and there’s actually a risk of something called “catastrophic forgetting,” where previously-solid knowledge is lost due to new experience.

More commonly, fine-tuning involves tweaking around the edges. For example, you might alter the weights of only the uppermost layers of the network, which tend to be less foundational. For example, lower level image processing may detect edges and shapes, while upper levels translate those primitives into complex figures like tumors or lesions.

Folks have also been experimenting with crazy math-heavy solutions like low-rank adaptation that using smaller parameter sets to impact the overall model. Don’t ask me how this really works. Math is hard; let’s go shopping.

In any case, none of this changes the fundamental situation — after fine-tuning, the model is still static. But it does provide an avenue to integrate new knowledge and help models grow over time. So that’s cool.

Retrieval-Augmented Generation

Another way of providing new data or concepts to a model is Retrieval-Augmented Generation (“RAG” — these folks love their acronyms). In this approach, models are provided the ability to fetch external data when needed.

The typical way “normal” folks encounter RAG is when asking about current events or topics that require context, like this (see the full exchange here or here):

I use Anthropic Claude for most of my AI experiments these days and have allowed it access to web searches. In this conversation you see the model looking for current and historic information about wildfires near Ventura, then drawing conclusions based on what it finds.

Model Context Protocol, Take 1

These days most RAG tools are implemented using Model Context Protocol, an emerging standard for extending AI models. MCP is a lot more than RAG and we’ll talk about that later, but in its simplest form it just provides a consistent way for models to find external information.

What’s really interesting here is that the models themselves decide when they need to look for new data. This is seriously trippy, cool and more than a bit freaky. As a quick demonstration, I MCP-enabled the data behind the water tank that serves our little community on Whidbey Island.

I’ve implemented the protocol from scratch in Java using JsonRpc2 and Azure Functions. I could go on for a long time about how MCP is bat-sh*t insane and sloppy and incredibly poorly-conceived — but I will limit myself to comparing it to those early Macintosh RAM days. Eventually we’ll get to something more elegant. I hope.

Anyways, MCP tools of this variety (“remote servers”) are configured by providing the model with a URL that implements the protocol (in my case, this one). The model interrogates the tool for its capabilities, which are largely expressed with plain-English prose. The full Water Tank description is here; this is the key part:

Returns JSON data representing historical and current water levels in the Witter Beach (Langley, Washington, USA) community water tank. Measurements are recorded every 10 minutes unless there is a problem with network connectivity. The tank holds a maximum of 2,000 gallons and values are reported in centimeters of height of water in the tank. Each 3.4 inches of height represents about 100 gallons of water in the tank. Parameters can be used to customize results; if none are provided the tool will return the most recent 7 days of data with timestamps in the US Pacific time zone.

Other fields explain how to use query parameters. For example, “The number of days to return data for (default 7)” or “The timezone for results (default PST8PDT). This value is parsed by the Java statement ZoneId.of(zone).” Based on all this text, the model infers when it needs to use the tool to answer a question, like this:

Access the full exchange here or here.

*** IMPORANT ASIDE *** If you look closely, you’ll notice that the model seriously screwed up its calculation, claiming a current tank volume of 4,900 gallons, when its maximum capacity is actually 2,000. If you click the link to the full exchange, you’ll see me call it out, and it corrects itself. This kind of thing happens with some regularity across the AI landscape — it’s important to be vigilant and not be lulled into assumptions of infallibility!

This is an amazing sequence of events:

  1. The model realized that it did not have sufficient information to answer my question.
  2. It inferred (from a prose description) that the Witter MCP tool might have useful data.
  3. It fetched and analyzed that data automatically.
  4. It responded intelligently and usefully (even with the math error, the overall answer to my question was correct). Pretty cool.

Large Context: Windows

Folks are also trying to help models learn by providing extra input in real time, with each interaction. For example, when I ask Claude “How would you respond when a golfer always seems to hit their ball into sand traps?” I get a useful but clinical and mechanical set of tips (see here or here). But if I provide more context and a bunch of examples, I can teach the model to be more encouraging and understanding of the frustrations all new golfers experience:

Access the full exchange here or here.

Now, providing this kind of context (known as multi-shot prompting) every single time is obviously stupid. But, for now, it gets the job done.

Early models had small context windows — they just couldn’t handle enough simultaneous input to use a technique like this (ok my little contrived example would have been fine, but real-world usage was too much). But these days context windows are enormous (Claude is currently in the middle of the pack with a 200,000 token window, where each English word corresponds to roughly 1.5 tokens).

Large Context: History

Say we’re at the market and they have a sale on bananas. You ask me if I like them, and I say no, they are gross (because they are). When we move to the bakery, you’re not likely to ask if I want banana muffins, because you remember our earlier interaction.

As we know, AI models can’t do this — but they can simulate it, at least for sessions of limited duration (like a tech support chat). We simply provide the entire chat history every time, like this:

Models are fast enough, and have large enough context windows, that we can do this for quite a long chat before the cost really kills us.

But eventually it does — and so we keep hacking. One technique is to ask the model itself to summarize the chat so far, and then use that (presumably much shorter) summary as input to the next exchange. If the model does a good job of including important ideas (like my distaste for bananas) in the summary, the effect is almost as good as using the full text.

Even this has limits. When the session is over, the model snaps right back to it’s statically-trained self. At least Lucy had that VCR tape to help her catch up.

Model Context Protocol, Take 2

We’ve already seen how MCP helps connect models with external data. But the protocol is more than that, in at least two important ways:

First, MCP enables models to take action in the real world. Today these actions are pretty tame — setting up online meetings or updating a Github repository — but it’s only a matter of time before models are making serious decisions up to and including military action. That’s far beyond our topic for today, but don’t think for a moment it’s not part of our future.

Second and more relevant to this post, MCP is intended to augment the innate capabilities of the model itself — we’re already seeing MCP tools that increase memory capacity beyond internal context windows.

MCP is stateful and two-way. The model asks questions of the MCP server, which can turn around and ask questions of the model to clarify or otherwise improve its own response. We’ve never been so close to true collaboration between intelligent machines. It’s just, for now, an ugly bear of spaghetti mess to get working.

What an amazing, scary, privileged thing to being living through the birth of artificial sentience. But as always, it’s the details that make the difference, and we’re in the infancy of that work. Impressive as they are, our models are static and limited — so we hack and experiment and thrash, trying to figure out where the elegant solutions lie. We’ll get there; the seeds are somewhere in the chaos of fine tuning, context windows, RAG and MCP.

Until next time, I highly recommend you check out Lucy’s story — it’s fantastic.

Interaction at the Edges

There’s a rule of multithreaded programming that says that if something can happen, it will. Package delivered at the same time the kitchen catches on fire and ALF is on live TV? For sure. I’ve been in countless debugging sessions where things that “can’t” happen absolutely, 100% happen.

Users are clever

Users are the same way. They may not all be tech savvy, but they’re incredibly creative. As with most things in my career, I first really learned this in the early 90s on the Microsoft Works team.

Works included simple desktop publishing features for making newsletters, invitations, posters, that kind of thing. Our customer service team sent us a case they were stuck on — the app would no longer let a user add content to their newsletter. It was a simple one-page document: header, footer, a few columns of content, maybe an image or two. That’s it. They tried saving a copy and using the new file, but no luck. They really didn’t want to start from scratch (I think they’d inherited the document from their predecessor).

The aha moment finally came when the rep asked the user to describe every action they were taking. Take last month’s article content, drag it off the page, add a new …. wait, what?

It turns out that this user didn’t know how to “delete” content blocks. But they realized that objects outside of the page boundaries on screen didn’t print — so each month they would just drag the old content blocks off the page and add new ones. Genius!

Except of course, the file got bigger and bigger and slower and slower until it just broke. I don’t remember if it was a memory problem, or if there were limits on the number of objects in a file, or what — but either way, a little education on “delete” and the newsletter was back in business.

We never expected users to be confused about deleting things. We never expected them to consider the off-the-page area as part of the real working space. More subtly, we’d never thought much at all about “periodicals” that used the same template time after time. And all of that’s on us — the user just found a creative way to do what they needed to do.

Whose car is it anyway?

A couple of weeks ago we traded in our Tesla Model X for a Rivian R1S. If you know me you know how conflicted and sad I am about Elon (see here, here and here), but that’s a story for another day. We’ll take the Rivian on its first Cali road trip soon, and I’ll write up a comparison then. Stay tuned.

Before we traded in the Tesla, I logged us out of all the various accounts that we’d set up on the vehicle. At the Rivian service center we signed over the title, handed them the key fobs, and waved goodbye to “Miss Scarlet” as we drove our new car home. Done and dusted!

Later that day I got a phone notification that the Tesla doors were unlocked. When I opened the app it turned out that I was still fully in control of the car. Huh. I honked the horn a few times for fun and then moved on with my afternoon.

Now this isn’t really all that surprising — of course Tesla didn’t know we’d sold the car; that’s not how it “works” in the industry. But it’s an interesting edge case, and one I thought about frequently over the course of the next week as Miss Scarlet made its way through the resale process. I didn’t snap pictures of the car sitting at the Bellevue service center, but once it moved down to Kent I thought it’d be fun to keep a record.

First stop, Manheim Seattle Auto Auction. The Manheim facility is pretty huge; the car started in the middle of a huge lot, then next to a little outbuilding. It then appeared to move into a garage — probably for detailing — before bouncing from spot to spot in the lot again.

After a few days I got a navigation alert and found the car driving on its merry way to Worldwide Auto Group in Auburn. Two days later another alert and it was en route to a private home in Tacoma. I’ve masked out the address on that one because I’m assuming it’s an actual person who bought the car.

FINALLY, after eight days, a notification popped up on Lara’s phone that Worldwide was asking to take “ownership” of the Tesla — we agreed and and off she went into the sunset, leaving the Ventura Powerwall as the only Tesla product in our world.

What to make of this? Certainly I wasn’t “intended” to retain control of the car after I no longer owned it — but did it really matter? I think so — during this period I could see exactly where the car was, lock and unlock it, remote start, summon it if I was anywhere near by, and quite a bit more. It seems like bad guys have managed some pretty nasty stuff given a lot less access.

It’s always the edges

As someone who built their career around the craft of software engineering, it’s tough to get old and watch crappy AI and copy/paste code take over more and more of the world. Don’t get me wrong, it’s happening because mostly it does the job, and usually cheaper. But that doesn’t mean I need to like it.

Still, at least for now, the game is still on. Designing for the unexpected and the edges and future still matters, and those aren’t, so far, things the machines do well. Sometimes it’s an issue of technology and errors and such; more often it’s about user interaction. Don’t write us off quite yet!

Pump and Dump Management

I’ve never been shy about my disdain for management “theory” — because let’s be honest, it’s not really that complicated. Have a plan, reduce complexity, take punches for your team, chip in. I’m not saying it’s easy, but the right move is usually pretty obvious. MBA strategies are just cover for folks that don’t want to do the hard work.

But sometimes they’re worse than just passive noise — they’re evil. Of course, disciples of evil strategies don’t call them that. But they’re pervasive and, for some folks, undeniably personally effective. After writing about memecoins the other day, it occurred to me that the worst of these could best be called “pump and dump management.”

Pump and dump managers are usually (but not always) hired from the outside. They parachute in with a lot of sound and fury, often show positive results in the short term by destroying long term value, and get out of Dodge while the getting’s good. Off to their next adventure, they ride these “successes” while avoiding blame for the true impact of their actions. It. Is. Infuriating.

Please, make sure you don’t hire these folks. But if it happens, send them on their way as quickly as possible — and pay your learning forward by warning that next hiring manager looking for a reference! Some key things to watch for:

The last guy sucked

PDMs love to talk about how bad everything is — and how lucky you are they’re around to fix it. Monoliths should be microservices; or perhaps microservices should be monoliths. Misaligned vendors need to be replaced with FTEs; or perhaps FTEs should be let go for more nimble vendors.

If schedules slip, it’s because they’re still “cleaning up” after their crappy predecessor. They probably need to swap out existing managers for folks they’ve worked with before. Things that somehow have supported the business for years need to be re-written from scratch. Sometimes they’ll dress all this up with false praise, like “it was probably ok back when the company didn’t have many customers.”

The best part of this dynamic is that it never ends. Nothing is ever the PDM’s fault; everything can always be traced back to sins of the “before” times.

Metric manipulation

Two things are true: (1) every good business runs on metrics, and (2) every metric can be gamed. It’s easy to increase sales if you start selling everything at a loss. Recruiting numbers can always be met by hiring underqualified people. Q1 costs look great if you stiff your vendors until Q2.

PDMs use their teams as personal labor — work harder, work longer, for ME. They claim personal credit for success, passing failure up the chain as if they had nothing to do with it. They flatter their bosses and never say no — even when it hurts their teams.

Honest leaders understand this, and use metrics to guide behavior with constant fine-tuning, interpretation and improvement. PDMs hit metrics at any cost — even at the expense of the company’s real goals. Slash and burn.

Punch down, kiss up

The best PDMs are master manipulators. By bottlenecking all communication through themselves they control the narrative, playing the savior while throwing all sides under the bus.

This always collapses eventually — but of course, a savvy PDM sees it coming and jumps to their next opportunity before they’re exposed.

Destroy relationships

Productive relationships require give and take. Trust and respect develop over time, as each side proves to the other that they’re committed to win-win exchanges. One party may get a bit more in one trade, knowing it’ll balance out in the next.

But PDMs don’t care about relationships, only transactions. And they typically have exactly one negotiating style: bully the other guy, spend positive capital created by others and call it “the art of the deal.”

  1. Make an outrageous first offer, so extreme that it knocks the other party off balance. Threaten and bully and generally be an unpredictable a**hole.
  2. Act like you’re doing them a favor by reducing your demand a bit.
  3. Declare victory.

The thing is, this often works — once. Or maybe even twice depending on the relationship. So it’s great for the PDM, who signs a few “great” deals and jumps ship for the next opportunity before the destruction catches up with them. The companies they leave behind pay the price.

I’ve been lucky through most of my career; only a few times was I on the receiving end of a PDM. But those times were the worst (friends, IYKYK). And now there’s a PDM in charge of my country. Blaming his predecessors, destroying long-won relationships, pointing fingers at everyone but himself, jumping from issue to issue so he never pays the price of failure. Truth is, he’s really good at it — and we’re left holding the bag.

Endnote: I get that the “evil boss” images I’ve scattered about here don’t really represent the specific PDM phenotype — they’re bad in all kinds of different ways. But we see suffer with enough photos of the master PDM every day, and I’m not about to add to that sorry display. So just enjoy some great movie memories … maybe a rewatch is in order!

How the $TRUMP scam works

So much corruption sails through American headlines these days, it’s become hard to pay appropriate attention to any one outrage. And of course that’s the point — shock and awe until it’s completely normalized and we just let it go. So in the spirit of not letting it go, let’s talk about one example that I actually can speak to in some detail: the $TRUMP memecoin.

You’ve probably heard about it in the news. Just before taking office in January, Trump owned and affiliated companies (basically the same folks selling his shoes and bibles and other shlock) launched a crypto “coin” branded $TRUMP and promoted by the jacka** himself. Its value quickly soared before steadily dropping to the $14 or so it is today, still with a market cap in the billions.

So just what is a crypto “memecoin” anyway, and why did he bother? The TLDR is at the end — but hopefully you’ll find the longer story illuminating too. Let’s dig in.

Tokens and “Coins”

Crypto “coins” are just crypto tokens, so we have to start there. If you want to go even deeper, I’ve written about crypto and blockchain stuff more generally; see here, here and here.

It’s useful to start by thinking about tokens like baseball cards. At the beginning of the season, Topps (or Fleer or whoever) prints up a bunch of cards that make up the “supply.” The cards themselves don’t have any intrinsic value, they’re just cardboard. People can buy the cards from Topps, they can trade or sell them to other individuals, and the price goes up or down based on how much people want them. Easy peasy.

In this case it’s better to think about it as if every card in the supply was just Cal Raleigh — so it doesn’t matter which specific physical card you have, they’re all exactly the same. That’s the difference between “fungible” (every instance is the same) and “non-fungible” (every instance is unique) tokens.

Fungible tokens are everything in the world of crypto finance. One of the most popular platforms on which to deploy them these days is Solana, which in all the ways that matter is the same as the OG Ethereum I’ve talked about before. Solana includes a core “program” that makes it super-easy for anyone to define a token and “mint” instances of it, with no need to write their own code.

$TRUMP is one of these. Trump’s merch companies used the Solana Token Program to define a token/coin with one billion instances (the supply). No intrinsic value, just data they made up on the Solana blockchain. Anybody holding $TRUMP tokens can interact with the program to move them into other wallets in return for a small transaction fee that goes to the Solana stakers (not the Trump organizations yet, stay tuned).

The Meme in Memecoin

Tokens are actually a pretty neat little tool. They can help broker access to limited resources, track rights in voting organizations, be used as currency in virtual (or physical worlds), and a ton more. The “memecoin” use case, however — at best it’s a toy, and honestly it’s just a scam.

Memecoins” don’t have a use, value or other reason to exist beyond amplifying some trend or capturing news cycles. Except they’re really good for stealing money from people, as in the uniquely American coin $HAWK promoted by the “Hawk Tuah” girl. Minters use viral techniques to con people into “pumping up” the value of their memecoin, “dump” their own holdings at a profit, and leave everybody else holding the bag.

What could be a more appropriate vehicle for the President of the United States to score some quick cash? To wit: 58 wallets have made millions from $TRUMP, 764,000 have lost money.

Trading Liquidity and Fees

But the grift goes way deeper than that. Sure, by holding 80% of the coins, even at $14 a pop they’ve “created” staggering wealth on paper. But there’s a great side game here too — liquidity fees.

Remember we said that anybody who holds a token can give it to somebody else by paying a small fee to the Solana stakers (the same fee that any transaction incurs). But that’s not the way markets typically work — I don’t go hunting for somebody holding Microsoft shares and ask to buy from them directly. Instead, “market makers” sit between buyers and sellers and grease the wheels. This basically happens in two ways (simplifying for my own sanity):

Centralized Exchanges (e.g. Coinbase)

Sites like Coinbase are “custodial” exchanges, meaning that they abstract away all of the crypto/blockchain complexity by holding users’ tokens for them in one big centralized pot.

The exchange then keeps a trading “order book” — lists of users that want to buy or sell tokens. The book matches up these users to fulfill orders automatically, floating the price up or down as demand indicates. No tokens actually move on the blockchain as part of these trades; it all stays in the Coinbase pot and they just remember who owns what.

Of course this relies on trust in the exchange, which isn’t always well-founded. Still, as crypto becomes ever more mainstream (for better or worse), exchanges are incented to behave conservatively.

Exchanges only do this for tokens with significant demand — most memecoins don’t make the cut. $TRUMP is an exception because of its “unique” brand advantage.

Decentralized Liquidity Pools (e.g., Raydium)

Here’s where things get more interesting. Centralized Exchanges are increasingly regulated and require users to prove their identity, report to the IRS, and so on. This is fine for most people most of the time, but can be unattractive to folks that want to trade anonymously (or more generously, without placing trust in a custodial exchange).

These users can instead trade via a “Decentralized Exchange” (DEX) like Raydium that uses “liquidity pools” and its own order book to facilitate exchange.

Any user can create a liquidity pool on Raydium by registering equivalent dollar values of two tokens into an account there. For example, I might create a pool that has $1,000 each of $TRUMP and USDT. Right now that’d be about 71 $TRUMP ($1,000 / $14) and 1,000 USDT tokens.

My pool is now available to the Raydium order book to fulfill orders. This gets a bit complicated, but bear with me. If somebody wants to buy 10 $TRUMP tokens from my pool, the system computes a price that will keep the product of the token count (71,000) constant:

  • Initial pool: 71 * 1000 = 71,000
  • Extracting 10 $TRUMP: (71 – 10) * (1000 + y) = 71,000
  • y in this case equals about 164 USDT, or $16.40 / $TRUMP

The platform adds a fee of around 0.3% to that $164, makes the trade on the blockchain, and shares a portion of the fee back to the owner of the liquidity pool (me). In short, I’m using my personal holdings to create market liquidity, and I get paid for it. Cool!

A side note: while Raydium isn’t a custodian of tokens in the same sense as Coinbase, in any real sense they are acting as one. When you commit your tokens to a liquidity pool, Raydium’s smart contract can move them at will. So there’s still trust involved — just a different kind.

Now remember that the Trump companies still hold about 80% of all $TRUMP tokens. They’ve used 10% of their holdings to create liquidity pools largely on the Meteora platform (equivalent to Raydium). And since they are such a disproportionate holder, their pools are party to many, many DEX transactions. Again, to wit: Trump’s meme coin business racks up fees as buyers jump at the chance for access to the president. Crypto data company Chainalysis estimates $320 million. Yikes.

“Buy my coin, meet me for dinner!”

OK, so we’ve established that the President is using the power of the United States to shake down naïve users for millions. But of course there’s no bottom for these people, so they’ve upped the ante even more.

A couple of weeks ago, the Trump companies announced that the top 220 holders of $TRUMP would be invited to a private dinner with the president at his club in DC. The top 25 will have a private reception with the jacka**. And of course, since that announcement the price of the coin has gone up significantly as people vie for access.

Now of course politicians sell access for funds all the time — hey, just last Monday Trump pulled in $1.5M a plate despite the fact that he can’t even run again. That’s its own huge problem of course, but at least there are some rules around disclosure and how the funds are supposed to be used.

Not so for the $TRUMP contest. The increased value and transaction fees that result from people vying for access here go directly to Trump’s companies and to Trump personally. It is the most obvious, blatant, unbelievable act of corruption one could imagine.

And remember how DEX-based transactions are completely anonymous? I wonder who is currently pumping up the value of $TRUMP so they can show up to dinner? Shockingly: Top $TRUMP buyers vying for dinner seats are likely foreign.

“Corruption Three Ways”

The mechanics are fascinating; unwinding it all is a game I typically enjoy. But the actuality of what is happening is just so craven, it ruins the fun:

  1. Trump is using his office to inflate the value of a meaningless asset for his own benefit.
  2. Trump is also profiting from fees incurred on almost every trade of the asset.
  3. Trump is openly advertising untraceable access in return for dollars.

We are so screwed.

Complex is just lots of Simple (Part 2)

This is the second in a two-part series; part one is here.

This has been a tough piece to finish — not because of the subject itself, which is super-fun, but because I keep getting distracted by unexpected behavior I want to understand. At nearly every turn, there’s something neat to see in this little world of evolving 2D cellular automata we’ve created. So bear with me as I try to boil down a lot of wandering into a few key points. There will be pictures!

Vertical Stripes and Hyperparameters

And the end of part one we taught our organisms to “black out” the grid — a simple task that could be optimally achieved with a single rule — and they did great. For the next few rounds I’ve made the goal a bit more difficult: turn the grid into a set of vertical one-pixel stripes, alternating black and white.

Our first fitness calculation for this is pretty straightforward: the first stripe can be either black or white, and the total number of correct pixels is divided by total pixels to get a fraction. Using a Von Neumann neighborhood and conservative parameters, the outcome was … horrible. Over three runs (details here, here and here):

Green is the best performance, red the worst and blue the average. A few pops but results regressed to 0.5 on every run — which is effectively a random grid (one out of every two pixels correct).

My first thought was, perhaps we’re just not getting enough variation. So let’s start tweaking the hyperparameters, i.e., the values that drive evolution. Mutation rate is an easy one, so we’ll increase that from 0-5% to 5-10% on each reproduction. Three more runs (here, here and here):

No love. Our changes did make a difference — there are more “pops” as we find potentially good solutions, but they don’t last and we regress again back to 0.5. But why? My next theory was that perhaps good solutions were being lost because they weren’t consistent. That is, a “random” rule is likely to get around 0.5 every time. But a rule that produces perfect stripes most of the time may perform terribly once in awhile. This corresponds nicely with real life — we don’t (usually) kick a decades-long good performer to the curb for a single failure.

To account for this I added a hyperparameter LastFitnessWeight, which attributes some fraction of fitness from the last iteration to the current one — the idea being that a success yesterday will lift your score today even if it’s an off day. Setting this to 25% gave these results (here, here and here):

Sad trombone noise. This is getting annoying — maybe the middle one showed some increased consistency, but really that’s just wishful thinking.

What we’re seeing here is one of the first rules (and a bit of a dirty secret) of digital evolution, and machine learning in general — hyperparameters don’t matter nearly as much as it seems like they should. With the right features and feedback you almost can’t help but succeed — and without them you’re usually hosed.

Fitness matters

Our fitness metric seems to make perfect sense — we know what each pixel should be, so the more pixels that are “correct,” the closer we are to a solution. But it turns out that that’s not quite right. Let’s look more closely at the history of one organism that did really well and then imploded:

This organism is the offspring of two parents that were basically generating random fields. About half of their pixels were correct, giving them fitness around 0.5 (see the blue highlights). For some reason this match created a really capable organism that for its first two generations delivered absolutely perfect (yellow highlight) scores — amazing!

But look what happened in the third generation (green highlight). It’s visually obvious that this is still a pretty good result, but because of the column skip on the left side (the double-wide white bar), all the pixels to the right were incorrect, so this promising organism was killed off (even with the history-preserving hyperparameter).

Tyranny of the mediocre

The end result of this dynamic is that over time the “interesting” organisms get squeezed out by mediocre but consistent ones (in particular all-white and all-black). This page details the final cycle of one such run: short-lived mostly random organisms at the top, newly-born random ones at the bottom, and a huge swath of 0.5 fitness blanks in the middle.

We can address this in two ways — both are pretty effective. The first is to simply use a better fitness metric. VStripesCombo combines two measures for a more balanced assessment:

  1. Stripey-ness” assesses the average length of a correct vertical stripe.
  2. Even-ness” rewards an even split between black and white pixels.

With this new metric, a solid block has fitness 0.25 (.5 for stripey-ness, 0 for even-ness), “interesting” organisms have a chance to succeed, and stripes emerge quickly. Finally, some success (here, here and here):

Another approach is to be more picky about who gets to reproduce. Our initial implementation kills off the bottom third of the population with each cycle, allowing the top two-thirds to reproduce. Since two-thirds includes that middle belt of consistent mediocrity, it can persist and grow.

Instead we can kill off the bottom half of the population, and allow each organism in the top half to mate twice. Just as with biological siblings, each mating crosses over and mutates differently, providing more chances for the strengths of the parents to compound.

As it turns out, this mode of reproduction also wins the day (here, here and here):

Strategies and weaknesses

The hallmark of evolved learning is solutions that our conscious, logical minds would never think of and often can’t really comprehend even after the fact. It’s frankly a little spooky. To wit, watch this organism solve the vertical stripes problem from random, along with the rules it employs. WTF man? (I have to say I do love the back and forth “wiggle” once it hits a final solution.)

All of these organisms were trained from a random starting grid. Running a few of them (all winners during training) from a single black pixel in the middle highlights two things: (1) their strategies are wildly divergent; (2) sometimes a strategy that tends to work in one case is an utter fail with a different starting configuration (last two examples below):

That second point can’t be overstated: you get what you train for — and we didn’t train for a single pixel initial state. Environment, fitness, reproduction rules, they all are critically important to the final product. This is going to come up again and again in the emerging world of AI. LLMs hallucinate because they have been rewarded for answering questions, not for saying they don’t know. We’d better get really, really good at this if we’re going to make it as a species (some more thoughts on that here).

You only know what you know

OK, enough with the stripes. For our next trick, let’s try to learn how to draw a frame around the edges of the grid — all white except for a one pixel rim around the edge. Seems pretty simple! Results are here, here and here:

Doh. It’s not even that it just doesn’t learn well — it doesn’t seem to learn at all. No matter what we do or how we define things, we can’t crack this nut. Why?

The answer is simple but important: there is simply zero information in the system about what an “edge” even is. Remember that the neighborhood computations “wrap” around so the grid appears to be an infinite plane. The edges are obvious to us when we draw the grid, but completely invisible to the organisms living inside it.

And you can’t “learn” something that you can’t perceive — it’s impossible, like asking a completely blind person to raise their hand when the lights come on. You can be mad about it, but it is what it is. This is surprisingly easy to forget, because evolved organisms are so good and finding subtle and non-obvious patterns, we just assume they’re omniscient. Nope.

OK, so let’s add an “edge” sense to our organisms by defining a new “relative” type in the Neighborhood class. When we include this new sense in our neighborhood, magic happens (here):

It’s a simple example, and perhaps not that shocking — by providing the boolean “edge” value, we enable the organism to effectively keep two sets of rules: one for the edges (turn them black) and one for everything else (turn them white).

But still, it’s cool. Just for fun, here’s a slightly less obvious example. By adding senses for which half of the grid a point is in (North/South, East/West), we can easily learn rules that expect different content in each quadrant (details here):

OK, that’s enough of a random walk for now. I could do this stuff forever, and each new lesson really does say something about evolution and learning in the real world. I hope I’ve put in enough eye candy to keep you entertained along the way, but even if I didn’t — it was good for me.

Wait just one more! I’ve been trying to teach some organisms how to split the grid diagonally, which proves to be a tough challenge. My best run so far is 5,000 cycles to get to a pretty consistent 0.95 fitness … but it don’t look great, folks. It feels like it has the right idea, but can’t settle into place (e.g., check out the lower-left quadrant here). Any ideas?

Complex is just lots of Simple (Part 1)

This is part one of a two-part series; part two is here.

Our world feels increasingly magic — I can have normal adult conversations with a computer; feel very much “in person” with my far-flung family playing VR minigolf; and sit back comfortably while my car drives me to California. Every once in awhile, we stupid, fallible humans build incredible, beautiful things.

But “magic” is also dangerous. When you rely on something that you don’t understand, you’re an easy mark. This annoys me every time I have to call in an expert to work on the house because I don’t know how to test (just a random example, not something that happened last month, but if it did happen, the guy was totally cool, I just don’t like being in that position) the pressure switch assemblies in my HVAC system.

Of course, the world is way too complex for us all to understand everything. But the good news is, complex things are just lots of simple things put together — and often understanding the simple version is good enough. If you know a bit about how real and fake neurons work, you can develop a pretty solid intuition for what LLMs are and aren’t good at. If you build a treehouse, you’ll gain some appreciation for how real houses work. And, the point of this article, if you code up a really simple genetic algorithm, full-blown evolution seems a little less supernatural (but a lot more awesome).

Evolution & Genetic Algorithms

The only honest knock on Darwin is just basic incredulity. “Come on, do you really believe that all the complexity of the human mind and body just spontaneously popped up, by random chance?” Often these are perfectly intelligent folks that believe evolution can do little things, like maybe select for sharper teeth in wolves — they just can’t buy the admittedly huge leap to a modern human.

And I get it, I guess. But the history of our species is basically just a long parade of thinking that things are magic or supernatural, figuring out that they’re not, and levelling up the magic another click until we figure that out too. So why are we always sure that this time is the one? Seems unlikely.

Watching simple evolution in action helps me buy that real evolution is comfortably up to the task of shaping the world we live in. And it turns out that building a digital environment in which to do that isn’t all that hard. It’s also super-fun, so let’s give it a try.

Genetic algorithms use digital versions of evolutionary concepts like crossover, mutation and fitness to iteratively solve problems. There are tons of ways to put them together; I wanted to start from scratch and build things up one step at a time. If you’re so inclined, I hope you’ll build and run the code yourself — it’s all open source and up on Github.

I should mention up front that I have basically no formal background in this stuff — I played with GA’s a bit in college but that’s it. So we’re truly exploring together here; apologies in advance if I do or say something stupid.

2D Cellular Automata

Before we get into the “genetic” part of all this, we have to create the world our evolving organisms will inhabit. For reasons that will become clear later, two-dimensional cellular automata provide a lot of advantages, so we’ll use that.

These worlds are two-dimensional grids of squares, where each square is “on” (black, true, or alive) or “off” (white, false, or dead). As time passes, the squares change value based on their current value and those of their neighbors (the cells surrounding them) according to some set of rules.

The most famous set of 2DCA rules is called “The Game of Life” — a surprisingly simple configuration devised by John Conway that generates satisfyingly rich behaviors. Life considers the cell itself and each of its eight neighbors (N, NE, E, SE, S, SW, W, NW):

  • If the cell is alive and
    • has 2 or 3 live neighbors, it lives on to the next cycle,
    • otherwise it dies.
  • If the cell is dead and has exactly 3 neighbors,
    • it comes to life in the next cycle,
    • otherwise it stays dead.

The only tricky thing about this is what happens at the edges of the grid, where there are no “neighbors” on one side or the other. Typically implementations “wrap” the grid around itself so that, for example, the neighbor to the west of square (0,0) is (dx-1,0) where dx is the width of the grid.

Life rules generate some pretty neat patterns — shapes that blink or oscillate, others that move across the grid, some that stay static, etc. The animation below shows a few common patterns in action; follow through to the Wikipedia page for an interactive version you can play with.

OK, back to business. Life rules generate some visually cool stuff, but they’re only one example of the tons of possible rule sets we could apply. That’s the crux of what we’re going to do here — use evolutionary processes to discover rule sets that accomplish something we’re interested in. The general approach is this:

  1. Establish a goal state for the world (grid). A very simple example of this might be “All squares of the grid are black.”
  2. Create a bunch of organisms (rule sets) at random.
  3. Let each organism exist for awhile and then measure how close it is to the goal.
  4. Kill off the worst and mate the best to replenish the population.
  5. Repeat.

In this model, each “organism” inhabits its own “world” — there is no direct organism-to-organism competition for resources or space. Obviously this is a departure from natural evolution, in which organisms typically go head-to-head. But there is still performance-based competition for mates, so it works out.

We’ll see this again and again — there are infinite ways to tweak an evolutionary process. The real world is so wide, and so basically eternal, that nature just tries them all. We have to be a bit more judicious, but there are still a ton of different levers to pull.

Concepts and Code

Bitmaps, Edge Strategies and Neighborhoods

The Bitmap class is the workhorse of this whole system. Space and speed are important, and we do pretty well at both by cramming the bits into an array of longs. Also as we’ll see later, the array-of-longs approach helps with some other evolution-y stuff.

This class also defines the EdgeStrategy enum, which defines how the class should respond when asked about a coordinate that is off the grid. We use the “Wrap” strategy almost exclusively, but the alternatives might be useful in specific cases.

The Neighborhood class encapsulates approaches to identifying the relevant context for a particular square in the grid. The rules of “Life” which we saw earlier use a Moore Neighborhood, which is basically the 3×3 grid centered on each square. The Von Neumann Neighborhood is also common, which excludes the diagonal corners of Moore. There are others as well.

The neighborhood defines everything that a square “knows” about its environment at a given point in time, so it’s obviously super-important to the learning process. We’ll see this in action in part two of the series.

Organisms, rules and “DNA”

Unpacked, real DNA is basically a sequential chain of four nucleotide bases (A, C, G and T). In order to apply genetic algorithms to digital organisms, their digital DNA must also be representable by a chain of primitive building blocks.

Digital DNA must also be resilient to random mix-and-match operations during reproduction. A single mutation in an organism’s genes can be:

  1. Irrelevant. Much of our DNA is “non-coding” and a mutation within these regions may be pretty much unnoticeable (ok it’s a bit more complicated than this, but close enough).
  2. Advantageous. A mutation may make the organism more fit for its environment — maybe the shark’s teeth angle back a little more to hold onto prey.
  3. Disadvantageous. Perhaps it makes the organism unable to create a particular enzyme, like the lactase that helps us digest dairy products.
  4. Catastrophic. A mutation may render the organism completely unviable due to a structural problem that stops the DNA from functioning at all.

Evolution doesn’t work very well if #4 happens with any real frequency.

This is a major design challenge for GAs, but manageable for our particular problem. The NeighborhoodRulesProcessor class uses our array-of-longs Bitmap approach to create a sequence of bits that can be easily manipulated, perhaps to advantage or disadvantage, but without damaging their viability.

This next bit is a little hairy; bear with me. Or just skip to the next section, understanding that our rule sets are represented by a resilient array of long integers. Here goes. Neighborhoods are stored as arrays of relative coordinates: e.g., (0,0) is the target square itself, while (-1,0) represents one position to the West. Neighborhood Rules assign each of these relative coordinates to a bit in an integer. For a Neighborhood that looks at X squares, this results in 2x possible integers: 32 for Von Neumann, 512 for Moore.

The “outcome” for each of these integers is either “black” or “white”, which we represent using a one-dimensional bitmap indexed on the integer itself. The array of longs underlying this bitmap is our DNA, which is pretty cool. Any bit in the rule set can be altered and we will still have a viable outcome — maybe better or worse, but never catastrophic. Sweet.

Fitness

Once our population of organisms has run for awhile, we need to asses how “well” each one did, so we know who should die off and who should hook up.

This is the job of the Fitness class. The simplest type is MostOn, which simply counts the number of black squares and reports it as a fraction of the total. The best possible fitness score in this case is 1.0 — solid black.

Fitness can really be anything measurable — in part two we’ll look at vertical stripes, alternating black and white one-pixel wide vertical lines. We’ll also see how different ways of measuring Vertical Stripes fitness can make a huge difference.

Selection

The Reproduction class sorts the population by fitness and uses that ordering to decide which organisms should reproduce (the best two-thirds) and which should die off (the bottom third).

The reproducing population is paired up using a strategy defined in the PairingType enum. The default is “Prom,” which pairs the organisms ranked #1 with #2, #3 with #4, and so on. Random mixes this up so anyone can pair with anyone. There are other ways to do this too — March Madness-style bracket seeding, anyone? It also might be interesting to change the proportions and allow some or all of the winners to have multiple offspring.

Again, this isn’t exactly the way it happens in real life. But it’s close enough — and we get more levers to play with along the way.

Crossover and Mutation

The last bit we need to code up is the actual reproduction between two organisms — combining the DNA of each parent into a brand new, novel offspring. This happens in two steps:

  1. Crossover takes subsections of each parent’s DNA to create a new sequence by picking a random set of indices to be “swap” points. Bits are taken from parent #1 up to the first index, then we start taking bits from parent #2 until we hit the next one, we swap again, and so on. The number of crossovers is random but subject to a configured maximum.
  2. Mutation takes the new DNA strand and twiddles a few bits at random, again subject to a configured minimum and maximum rate of mutation.

The new organism takes the places of one that didn’t make the cut, and we start the whole process over again. If things go the way we hope, maximum and average fitness for the population goes up and up until we, seemingly by magic, have found our answer.

Putting it all together: Evolving a blackout

Our first run will be a 25-cycle evolution of 200 organisms trying to turn all squares in their environments black. Each cycle will run for 200 iterations over a 100×100 grid initialized with a random pattern. (Fun fact: it’s a very poor choice to start with an empty (all white) environment for this challenge — can you guess why?)

We’ll use the same Moore neighborhood as we did for Life. We’ll use Prom-style pairing, allow a maximum of 10 crossover points, and mutate at a rate between 2.5% and 7.5%. This mutation rate is way higher than in nature, and while it can create some chaos, it also introduces novel configurations more quickly, which can reduce the number of cycles required to progress.

TLDR: the results are here.

…and it’s pretty cool! Now to be clear, this is an easy task. The single rule “no matter what is in my neighborhood, turn me on” will accomplish it in a single iteration. But our organisms didn’t know that. They each started with a totally random set of rules, and all we did is measure how that random set did, pick the best and mix/mate them together, and try again. This graph shows the best, worst and average fitness scores over each of the 25 cycles; by cycle 17 we’d found rules that seem to work perfectly:

Another fun way to look at this progression is to look at the best-performing result at key points along the way:

The results page for this run has a lot more detail — be sure to check it out. You can see the outcomes for every organism at each cycle, which really hammers home the trend of getting better over time. Clicking on any block will show you the history of the organism that created it, including its parents. These can be quite surprising — for example, the overall winner was born from two parents that each performed just barely better than random.

We’ll see more of this next time, but you can also get a sense for the different strategies that can emerge. In cycle 18 for example, there are dots and lines and blobs and all sorts of mechanisms at work.

Success is Usually Messy

The last thing I’ll call out from the blackout run is that evolutionary success is rarely what you’d expect. I pointed out above that you can achieve a blackout in one iteration with a single rule — but that’s not what our evolution produced.

Moore neighborhoods generate 512 individual rules, and that’s just hard to look at. So I ran the blackout evolution again using a Von Neumann neighborhood of 32 rules. Results for that run are here — similar except in this case we got really lucky and one organism hit perfect fitness on the very first run.

Anyway, the rules for the winning organism in this run look like this; the top section are rules that turn their cell white, and the bottom turn their cell black:

This is a super-effective rule; the organism ran for five cycles and was perfect every time. Looking at the rules visually, a few things pop out:

  • There are significantly more black rules than white.
  • Only four rules (highlighted in red) make the grid “whiter” — all others are either neutral or black.
  • Progress reinforces progress — ignoring the center value, all of the rules with three or more black inputs have a black outcome.

Run over 200 iterations, these rules are basically guaranteed to get us to a blackout. But it sure is a roundabout trip compared to the “optimal” rule (I’ve put an animation of our winning rule going through just 12 iterations at the end of the article). However, it’s important to understand that, given our fitness rules and environment, our evolved rule is exactly as good as that “optimal” one. As long as the blackout was attained by iteration #200, it did the job perfectly. Nothing about our world indicated that speed mattered — only the final outcome.

OK, that’s enough for this session. We’ve done a lot — learned about 2D Cellular Automata, wrote code that lets us mimic evolution in digital form, and even saw the first glimmers of some pretty cool outcomes. Next time I’ll get deeper into the weeds so we can really see how this machine ticks. There are just unlimited cool things in the world.

Always-On Attacks

My last article was full of nostalgia for the lifetime of hacks that have shaped my life and career. I touched on the real bad guys too, but basking in the warm glow of a CRT it’s easy to forget how relentless the ugly side can be. They are always, always “on” — and they only have to beat the good guys once to do a ton of damage.

We talk about network security using physical analogs — doors and keys and alarms and such. And that’s fine as far as it goes, but it completely underplays the insane scale of attacks happening on the Internet all day, every day. A more accurate picture is the zombie horde surrounding the mall in Dawn of the Dead, probing 24×7 for any vulnerability.

A quick illustration. I keep a server in the Azure cloud that I use to test early versions of an app I’ve been working on the past few months. I keep this machine turned off 95% of the time, spinning it up only when I want to preview a new feature or do a demo.

Last night I flipped the server on for about six hours. Before shutting it down, I took a quick look at the request log and despite myself I was again struck by the sheer volume of attacks. Somehow within ten minutes of powering up, the script kiddies found my server and started rattling the doors and windows. A small sampling taken from hundreds of attempts:

1. Secrets left in the open

/.env
/…/.env
/.git/config
/actuator/gateway/routes
/connector.sds
/_profiler/phpinfo

These are basically the equivalent of checking for a key under the welcome mat, flower pots, above the door jamb, or inside fake garden rocks. They’re files that may contain sensitive information like passwords, and are commonly hosted “accidentally.” Either they shouldn’t be on a production server at all, or the hosting web server is mis-configured to allow access. There’s a massive list of these — it’s really easy to slip up when deploying a large project.  

2. Remote Code Execution

/autodiscover/autodiscover.json?@zdi/Powershell
/cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/bin/sh
/hello.world?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input
/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
/ecp/Current/exporttool/microsoft.exchange.ediscovery.exporttool.application
/?XDEBUG_SESSION_START=phpstorm

These are all attempts to coerce my server into running code provided by the hacker. Sometimes the problem is debugging code deployed to production accidentally, kind of like the open secrets issue. More often these hacks exploit SQL injection or buffer overruns; the web server receives data from the user and accidentally executes it as code. Of course, if I can convince you to run arbitrary code on your server, you’re hosed.

3. Beacons and secondary attacks

/aaa9
/aab9
/alive.php
/t4
/teorema505?t=1

Once a hacker breaks into a machine, they’ll typically install backdoors or other software that makes it easier for them to keep control. One of the most popular apps for coordinating this is Cobalt Strike, which ironically was created as an “ethical” hacking tool to help good guys find vulnerabilities.

The first two URLs are “probes” checking to see if my server is running Cobalt Strike. If so, it’s probably in control of other hacked servers — basically bad guys trying to take advantage of other bad guys, or possibly good guys being sloppy with their tools.

Diversity helps!

Each of these attack types is interesting in its own right. But the really scary thing is that they represent just a fraction of the bad dudes hitting my server over only a few hours. It’s relentless — I’ve seen estimates that suggest that around 25% of all Internet traffic is hacking scripts (which doesn’t quite top porn’s 30% but is still pretty terrifying, go humans).

The one positive thing that sticks out is that — just like in culture, public health and farming — diversity helps. The hacks I catalogued were targeted at specific products: Microsoft Exchange, PHP, Git, Apache, Cobalt Strike, Cisco, Fortinet and more. Because I’m not running these, the attacks are impotent.

Of course, diversity isn’t the most efficient model of the world — many folks think I’m a little weird for running my own embedded web server. But it’s amazingly protective. So there’s my plug for robust anti-trust action in the tech industry.

In any case, just a glimpse. Keep your systems updated and please, for the love of God, don’t click that email link.

46+ Years of Hacking History

I just finished re-reading my copy1 of The Cuckoo’s Egg, Cliff Stoll’s detailed account of the German hacker that waltzed through the academic and military proto-Internet back in the Eighties. Reading this stuff brings me back in time, the way world events and other touchstones do for normal people. Almost a half-century of hacking history! Insane.

Disclaimer: While the culture has been extremely formative to my life, I was never a serious hacker. There were a lot of kids like me — fascinated by technology, curious, and a little drawn to the idea that adults didn’t like what we were doing. I certainly don’t recommend breaking any laws; there’s more than enough cool stuff in today’s world to explore without having to be a bad guy.

1978: It begins

My folks got me a TRS-80 Model I for my ninth birthday. My own computer, in my own room — it was absolutely unheard of, and while my parents were pretty great in a lot of ways, it’s not much of an exaggeration to say that this was one of the most consequential things they ever did for me. It was really mine — when I wanted to wire up a snooze button for the alarm program I wrote, I cracked that sucker open and soldered a switch right onto the board (with 16-gauge speaker wire no less). I can’t believe they let me do that stuff.

I learned to write BASIC programs by transcribing code out of magazines and finding my typos. I was unbeatable at whatever that car racing game was called. I tried (unsuccessfully) to sell my own game “Arrow Attack” by placing a tiny ad in Creative Computing. It Was The Best.

Unfortunately, the Model I was discontinued pretty quickly because it emitted an illegal and possibly dangerous level of radio interference. Even this was kind of awesome — I had a few games that manipulated the signals to broadcast sound effects to a nearby AM radio (they sounded like this, try harder 5G).

The 80s: Phreaking and WarDialing

I continued to write code through middle school and high school, but was really more enamored with networks and modems. Back then the phone company still “owned” every piece of equipment connected to the network, and it was illegal to use a modem without getting authorization. They claimed it was protect their lines from damage, but really they were just monopolistic a**holes, so we ignored that. The AT&T breakup in 1984 paved the way for all kinds of awesome stuff, not the least of which was the Sports Illustrated Football Phone.

Anyways. “Phone phreaking” — using tech to control telephone lines and billing — had already been around for years when I learned about it, and some of the exploits were already being patched by the Baby Bells. But enough were still active to make things fun. For example, most payphones “told” the main office about events like coin insertions by playing specific audible tones — so if you (theoretically of course) had a machine to generate those tones, you could connect calls without using actual coins.

Phreakers maintained a huge list of “boxes” that could perform various feats. The only one I ever built was a “black box” — really just a resistor activated in-line with the phone. When mechanical switching equipment connected a call to start the phone ringing, voltage on the line was pretty high. Picking up the phone dropped that voltage, which was detected at the substation and used to start billing. Deploying a black box would reduce the voltage just a bit — enough to stop the ringing but not enough to trigger the billing event. Since the line was already connected, you could talk away and never get charged.

The only trick about a black box was that it worked on the receiving end of a call — so it was primarily used by folks hosting “BBS” software, enabling users to connect inbound for free. I spent a lot of time connecting our computer (by then a Compaq Luggable my Dad used for work) to these “Bulletin Board Systems,” messaging with folks around the country and world.

Unlike today’s always-on social media, a BBS was more like a drop box. Users would dial directly into the BBS via receiving modems connected to one or more dedicated phone lines. You’d read and respond to messages, upload and download files, then disconnect so somebody else could use the system. There were hundreds of these online in the mid 80s; I’d sit at the computer into the wee hours of the morning, listening to WAAF and bopping from one to another.

The currency of BBS users was typically software or “text files.” I was a fan of text — thousands of people (of widely varying intent and ability) wrote about everything: science fiction, sex, hacking and phreaking, anarchy, radio, survival … everything. Seriously, check out textfiles.com, they’ve created a huge archive and you can get lost in there for years. So much garbage, but also an amazing repository of decentralized, censor-free, citizen-created knowledge at a time when there Was No Internet. Intoxicating, especially for a young teen in the boring suburbs.

Small homebrew BBS’s gave way to commercial services like The Well that used subscription fees to support more phone lines — enough for real-time conversations between users. And those in turn gave way to the big boys like Compuserve and AOL. But man, those early days were fantastic.

In parallel with all of this, the Internet was quietly being built at academic and military computing centers around the world. Most of these systems could be accessed through modem connections as well, and from there a user could connect around the world.

This was the world of The Cuckoo’s Egg, and a ton of popular culture and media-driven fear about espionage and all the worries that come with every new technology. The anthem of my personal circle was WarGames; there was nobody — nobody — as cool as David Lightman.

Modems connected to the ARPANET/MILNET were a hot commodity, and (thanks to WarGames) we knew that the way to find them was a WarDialer. I wrote my own (in BASIC of course) which scanned every phone number in the local calling area around Lexington, MA — special because it searched numbers out of sequence, an attempt to evade detection by the phone company. Hello, Route 128.

Early 90s: First Winter

I really learned to code during the tail end of the 80s and early 90s. Despite a bunch of time writing in BASIC, I didn’t really have a clue about the craft that is software development until I got to Dartmouth. A computer science department small enough to know everyone but big enough to go deep, a Mac for every student, and fully-networked dorm rooms! And after that, my early career at Microsoft kept me pretty heads-down for a few years …

… which was a good thing, because it was a pretty lousy time to be a curious hacker. There were some interesting trends for sure — buffer overruns, viruses and worms all involve neat technical problems. But mostly it was a time when the a**holes took the wheel.

Chaos seemed to be the point. Hackers vied to see how far their viruses could spread and made sure their names were attached. Sometimes they caused damage on purpose; more often they just wrote bugs and screwed up systems by mistake.

I’ve always been annoyed by the flak Microsoft took during this time. Windows always took the blame, but it was the preferred target because it was the most popular operating system in the world, and because it was a platform for thousands of independent developers building their own businesses. Sure the company could have reacted more quickly, but everybody was caught flat-footed at first. Ah well.

Anyways, after a pretty nasty arms race, the platforms figured out how to release patches quickly, users learned to be more careful, and things settled down a bit as we entered the second half of the 90s. Then along came the next twist.

Late 90s and Early 00s: The Internet Emerges

The Internet bubble was an incredible ride. All of a sudden, everything was a website. People were actually using the Internet to buy things — with real money! — but the technology was in its infancy and nothing was off-the-shelf.

At drugstore.com (a great example of the business plan “What if we took blank and put it online?”), we built one of the very first large-scale eCommerce experiences. Shopping carts, online promotions and coupons, affiliate programs, secure payments (you could even USMail us a personal check!), live inventory management, automated replenishment, prescription refills (admittedly mostly for Viagra and Propecia), contextual advertising … The list was long and fun and we were breaking new ground every day. What a rush.

And of course, all that brand new technology was fertile ground for new hacks. A few examples:

  • While almost nobody ever used this, the Windows NTFS file system actually allowed one file to contain multiple “streams” of data which were addressed by using the format FILENAME::STREAMNAME. The “default” stream was called $DATA, so by fetching a page like http://somesite.com/myexecutablescript.asp::$DATA, you could convince IIS to return source code. This code often contained passwords or other secrets helpful in digging into a site.
  • SQL Injection hacks were everywhere; almost nobody fully protected against them in the early days.
  • Silly and simple, for some reason we thought that hidden urls named things like /test and /admin would actually stay hidden. Search crawlers also found documents nobody ever meant to be public; to this day searches like passwords filetype:xls routinely return sensitive data.
  • Identifiers like order numbers and user identifiers were often issued sequentially, very helpful in accessing information beyond your “allowed” scope.

Most of these were notable because they came and went so quickly; everything was moving so fast that open discussion truly was a public service. And of course the pace of innovation slowed when the money evaporated, which gave the second wave of sites time to catch up.

10s and 20s: Second Winter

During the last decade criminal hacking activity has gone nuclear — organized crime and state actors have figured out just how cheap and powerful hacks can be. Sadly, they’re not generally even very interesting — mostly phishing-initiated attacks that convince somebody to disclose credentials or other sensitive information, used for data ransom and identity theft. There’s nothing clever about social engineering; it’s just ugly and wrong.

In the less-purely-evil arena, the “Internet of Things” has been having its day in the hacking sun. It’s the same old pattern — rapid innovation around digital smarts in our appliances, cars, healthcare devices and homes has outpaced effective security. The good news is that we know how to catch up, and the ecosystem is doing so pretty reasonably.

Radio-based devices are being exploited as well. One of the most interesting (but unfortunately cheap and lucrative) hacks is the keyless entry amplifier. Many cars now automatically unlock as you approach with your key fob. There are a few ways the unlock can be initiated, but the basic idea is that your fob emits a low-power radio signal with a unique security code2. The signal is only strong enough to reach a few feet, so your car “hears” it when you get close and can respond by unlocking the doors. If a hacker can get physically near to your fob (say outside a window near your home office, or next to your purse at a coffee shop), they can amplify the fob signal to a receiver near the car. This amplifier doesn’t need to understand the codes, it just needs to relay the signal from the fob to the car and … poof!

So what’s the verdict?

For better or worse, new technology goes hand in hand with ways to break it — and there are always bad guys ready to take advantage. The world of “white hat” hacking can be sketchy and fraught, but I think it’s proven itself to be an essential part of the innovation cycle — pretending the holes don’t exist is not a recipe for success.

Lots of folks disagree with this — to paraphrase Stoll, we don’t thank a burglar who takes advantage of an open door. That’s a super-legitimate perspective, and there’ve been many instances where “ethical” hackers accidently wrote their own bugs that went disastrously wrong (e.g., the 1988 RMS worm). So where’s the line?

It’s hard to say, and certainly not one easily parsed by hormone-addled teenage brains! But there’s no question that the discoveries, problems and solutions behind almost a half-century of hacks turned me into the developer that I am today, so it ain’t all bad. I try to break my own stuff, and count coup on my friends when I find flaws in theirs. The coolest stuff rarely sits in the middle of the road.


1. Back in the early 1990s I built a little toy for the Macintosh called Mouse Odometer, a background app that measured mouse travel in miles. MO was shareware with a requested donation of $5; sometimes folks would send other stuff instead. Cliff Stoll sent me a copy of his book!

2. The back and forth here is usually more complicated; constantly broadcasting a signal drains a fob battery pretty quickly. Instead, usually the car is constantly broadcasting a low-power “wakeup” signal that causes any of the fobs in the vicinity to start doing their thing. Passive RFID technology and the transfer of power by radio is basically magic.