Let me share a story. A team is halfway through moving off one UI library and onto another. Say they are leaving MUI and going to shadcn/ui. The sensible way to do that is incrementally, so for months the codebase holds both. The old way, which most of the files still use, and the new way, which is where the team has decided to go. Every experienced engineer on the team carries that distinction in their head. They know which pattern is legacy and which is the direction, and they never have to think about it, because they were in the room when the call was made.

Then Kim joins.

Told once, on a busy first day

On Kim's first day, someone gives them the overview. Here is the app, here is how we deploy, and by the way we are migrating off the old component library, so new work should use the new one. It is a cursory pass. It is also true, and if there had been an onboarding buddy to reinforce it over the following weeks, this post would not exist.

There was no buddy free that month. So Kim got put on what new joiners usually get put on: bug fixes and small amends, the low-risk work that helps someone learn a codebase without breaking it. All of that work was in the old code, because most of the codebase is the old code. For four weeks Kim read legacy patterns, matched legacy patterns, and shipped small fixes that looked exactly like everything around them. That is the job working as intended. It is how you learn a system.

The one thing that never came up again in those four weeks was the new way. The day-one telling, one sentence among fifty, faded the way a single instruction fades when nothing you touch reinforces it. By the end of the month the migration was no longer a live thing in Kim's head. It was not forgotten so much as never made real. What was real was the code Kim had spent a month inside, and that code spoke almost entirely in the old dialect.

The trust Kim earned, and how it came apart

Kim did well. The fixes were clean, the reviews were smooth, and the team decided Kim was ready for a feature to build on their own. The new hire earned their standing and gets handed something real.

Kim built the feature the way a month of work had taught them to build things. The old way. It was internally consistent, it matched the surrounding code, it passed review, and it went live.

Some weeks later it broke. A senior engineer picked up the incident, traced it, and fixed it, and in the course of fixing it saw how the feature was built. Then came the question, the one that stops being about the code and starts being about the person who wrote it. Why is a brand-new feature written in the pattern we are trying to get rid of. Who is still adding to the pile we are trying to shrink. Kim was made to feel careless. The senior engineer felt let down by someone they had chosen to trust. The trust the team had built up over a good month took a real hit, and so did Kim's sense of standing on it.

Nobody in that story did anything wrong.

Kim followed the only pattern a month of immersion had given them. The senior engineer saw new code written in the old pattern and concluded that whoever wrote it had not been paying attention. Nothing around that code suggested otherwise, so it was a reasonable conclusion to reach. The team's decision to migrate was a real decision, made for good reasons. It just was not reachable, and it was not reinforced, at the one moment Kim actually needed it, which was while the code was being written. Common and correct looked identical from inside the migration. The only signal that told them apart lived in the heads of the people who had been there at the start, and Kim had not been.

Now replace Kim with an agent.

Every session is a fresh newcomer

An agent has no first day and no month of immersion, which sounds like it should help and does the opposite. When an agent needs to know how this team does things, it does the same thing Kim's month did for Kim, only faster: it reads the tree and grounds itself in what it finds. The tree is still mostly legacy. So the agent reintroduces the superseded pattern for exactly the reason Kim did, because the codebase taught it that this is how things are done here.

The difference is one of scale and memory. Kim made this mistake once, learned the lesson painfully, and never made it again. An agent makes it several times a day, and it cannot be taken aside and told once, because the version of it that was told is gone by the next session. Every session starts as a fresh newcomer reading the same mostly-legacy tree and drawing the same reasonable, wrong conclusion. You cannot onboard your way out of it. The gap that caught Kim is the same gap, reopened on every run.

Humans and agents fail here in the same way, for the same reason. The decision was real, and there was nowhere to meet it at the moment it mattered.

Where this actually went wrong

It is tempting to file this under training, or onboarding, or code review, and each of those would have helped at the margin. But the load-bearing failure is narrower than any of them. A real decision was made, the decision to move off the old library, and then it evaporated. It survived as a habit in a few people's heads and as a minority dialect in the code, but in neither of those forms could a newcomer ask it a question.

Imagine the decision had instead been captured somewhere the code lived, so that the next contributor could ask it directly. Is this how we do things now, or something we moved off. And the answer was there to be had. Superseded, we moved to the new library in March, here is why. That answer is only useful at one moment, which is while the code is being written, not weeks later in the wreckage of an incident. The whole cost of Kim's story is the distance between those two moments.

This is what Spelunk is for. A decision like the migration is a first-class thing you can record against the code, and superseding an earlier decision is a first-class, recoverable move, so the trail reads as this replaced that in March rather than as a single mutable fact that quietly overwrote itself. Querying a decision is the core of the tool. The point is not that the answer is clever. The point is that the answer is reachable by whoever writes the next line, whether that is Kim or the agent, without needing to have been in the room a year ago.

What we're actually claiming

We are not claiming a tool that supervises your team. Spelunk does not review pull requests, it does not watch over your junior engineers, and it would not have flagged Kim's feature or caught the change in review. Spelunk ships nothing of the sort, and building a review surface is not something we're trying to do.

The claim is smaller and, we think, truer. The decision that would have spared everyone in this story was a real decision that genuinely existed and then evaporated, because the only places it lived could not be asked a question. Keeping that decision reachable, so the next contributor can ask it before the code is written rather than reconstruct it after something breaks, is the specific and modest thing we do. It does not make anyone a better engineer. It just means common and correct do not have to look identical from the inside.

git tracks what changed. Spelunk remembers why.


Spelunk is open source and code-aware, built to be called from whatever agent you already use. Repo and docs: spelunk.cloud.