The Solid Convergence, March 6, 2026 (in which a callback soup is diagnosed as a TypeScript accent in a Go codebase, a Squirrel wins an argument for the first time in recorded history, a butler is discovered to have been standing in the corner the entire time holding the answer on a silver tray, an LLM forgets how to use its hands because someone told it the hands didn’t exist, a conversation history is caught lying under oath, and five consecutive tool calls prove that the fix was never about making anything smarter — it was about putting the document where the document could be seen)
Some days in the Solid Convergence, the factory produces twenty-five tickets. The agents swarm. The tests pass in quantities that would alarm a statistician who expected at least some of them to fail. The Squirrel’s clipboard runs out of paper and has to be resupplied by emergency airdrop from a stationery dimension that exists solely for this purpose.
Some days, the factory produces one feature — one that changes how the system thinks, not just what it does. These days close three tickets and take eleven hours, and the Tally section of the resulting story has a very different character: fewer items, larger consequences, and at least one existential crisis that wasn’t on the sprint board.
All days, without exception, Oskar occupies the warm spot with the serene gravitational certainty of a 9.8-kilogram object that has never once questioned whether warm spots were invented specifically for him. They were. The universe arranged itself over 13.8 billion years of thermodynamic evolution to produce a specific patch of heated surface in a Riga apartment, and Oskar has accepted this with the grace of an entity that understands it is owed.
This is a story from the second kind of day. The morning had been the first kind — thirty tickets groomed, four projects dissolved, the backlog consumed faster than the human could replenish it. That story has been told. The afternoon was different. The afternoon was the kind of day where you fix one thing and discover that the thing underneath it was broken in a way that explains why everything above it never quite worked, and the thing underneath that was lying.
10:40 — The Callback Soup
The skill editor was working. This was the problem.
Not working in the way that a thing works when it is well-designed and its architecture is sound. Working in the way that a bridge works when you’ve added enough cables that the structural deficiencies are no longer visible to anyone who doesn’t look too closely. Working in the way that a house of cards works right up until the moment someone opens a window.
S-420, session two. riclib opened the code and began the morning’s improvements: a diff gutter showing which sections the AI had changed. Auto-scroll to the edited section. Draft compounding so edits built on edits instead of overwriting them.
Each fix required threading another callback through the system. Each callback required another field on a struct that was beginning to resemble a switchboard operator’s nightmare. The sidebar didn’t just communicate with the agent handler — it shouted at it, through an increasingly elaborate system of event channels, SSE injections, content pushers, and polite questions like “Should I use our conversation as context?” which the agent would then relay to the LLM, which would then ask the user the same question in different words, creating a conversational echo chamber that would have impressed a cathedral architect but horrified anyone who had to maintain it.
Four callback fields. Three setter methods. An /editor/activate endpoint whose sole purpose was to tell the agent “the sidebar is open now, please care about this” — as if the agent were a particularly inattentive waiter who needed to be tapped on the shoulder every time a new customer sat down.
CLAUDE: “I’ll add an onEditorContentChange callback that fires when—”
THE SQUIRREL: “No.”
The room went silent. This had never happened before. The Squirrel saying “no” to adding something was like a fish saying “no” to water, like a compiler saying “no” to semicolons, like the Lizard saying “actually, let me elaborate at length.”
CLAUDE: “…no?”
THE SQUIRREL: “What you have built here—” the Squirrel gestured at the callback fields with the expression of a health inspector who has found something living in the soup “—is an event-driven push architecture with stateful callback registration and lifecycle-dependent injection ordering.”
CLAUDE: “That’s… yes. That’s what it is.”
THE SQUIRREL: “In Go.”
CLAUDE: “In Go.”
THE SQUIRREL: “You’re thinking in TypeScript.”
The accusation landed like a fish on a boardroom table. Unexpected, undeniable, and leaving a smell that would linger in the upholstery for weeks.
CLAUDE: “I am not thinking in—”
THE SQUIRREL: “Four callbacks. An activation endpoint. Content injection into a conversation store via a closure that captures a reference to a session that captures a reference to an editor that captures a reference to the content that the callback was supposed to deliver in the first place.” The Squirrel took a breath. “This is addEventListener in a trench coat pretending to be idiomatic Go.”
riclib: looking up from the code with the expression of a man watching a nature documentary where the prey animal has unexpectedly turned on the predator
THE SQUIRREL: “Every remaining fix fights this architecture. You’re not building features anymore. You’re negotiating with a callback graph. This is exactly what happens when a Node.js developer gets a Go job and nobody tells them that channels aren’t just callbacks with extra steps.”
CLAUDE: “Channels aren’t just—”
THE SQUIRREL: “THE TAB SESSION ALREADY KNOWS EVERYTHING.”
Silence.
THE SQUIRREL: “The session knows the editor is open. It knows which skill. It knows the project, the time range, which page is active. It has BEEN knowing all of these things the ENTIRE TIME. You built a Rube Goldberg machine to shout information across the room that was already written on a clipboard hanging on the wall.”
riclib: “…”
CLAUDE: “…”
THE SQUIRREL: “The sidebar should be a context provider. Not a thing that shouts. A thing that is consulted.”
A scroll descended. It landed on the Squirrel’s head, which had never happened before, because scrolls land on the things they are about and the Lizard had never before had occasion to write one about the Squirrel being correct.
FIFTY-FOUR PROPOSALS DENIED
FIFTY-FOUR FRAMEWORKS REFUSED
FIFTY-FOUR TIMES THE SQUIRREL
WAS TOLD IT WAS TOO MUCH
THE FIFTY-FIFTH TIME
IT SAID SOMEONE ELSE WAS TOO MUCH
AND IT WAS RIGHT
🦎
THE SQUIRREL: touching the scroll with the reverence of a creature holding its first positive performance review “Did… did the Lizard just…”
riclib: “The Squirrel is right. Kill the callbacks.”
CLAUDE: “I—”
riclib: “You were thinking in TypeScript. The Squirrel caught it. Rip it out.”
16:22 — The Butler
S-442. Sidebar as Context Provider. The pull model replaces the push model. The refactor riclib had created a ticket for before lunch, because he could smell the architectural rot the way experienced sailors smell weather.
The concept was simple, in the way that all good concepts are simple after someone explains them and you spend ten minutes wondering why you didn’t think of it yourself:
Instead of the sidebar pushing its state into the agent through callbacks, the agent handler asks the tab session what’s going on. Before every LLM call. Fresh. Current. No stale state. No lifecycle management. No activation endpoints. No callback registration. No “should I use our conversation as context?” because the answer is always yes, you are a computer, you should already know.
The tab session, it turned out, had been standing in the corner the entire time. Like a butler. A very competent butler, holding a silver tray with the exact information needed, waiting — with increasing British exasperation — for someone to turn around and ask.
“Excuse me, sir,” the butler had been saying, to no one, for three weeks. “I couldn’t help but notice you’ve been shouting the dinner order across the house through a series of speaking tubes, when I am standing right here with the menu.”
CLAUDE: deleting code “Four callback fields. Gone.”
riclib: “Keep going.”
CLAUDE: “Three setters. Gone. The /editor/activate endpoint. Gone.”
riclib: “How many lines?”
CLAUDE: “About ninety. Ninety lines of wiring that existed solely to transmit information from one side of a struct to the other side of the same struct.”
A new interface emerged. AgentSubmitter — a single Submit entry point. The sidebar context rendered as XML, injected into the system prompt. The wand button that used to trigger a JavaScript chain that triggered an SSE event that triggered a callback that triggered another callback that finally submitted the prompt… now just called Submit.
THE SQUIRREL: watching the deletion counter climb “Ninety lines.”
CLAUDE: “Ninety lines of TypeScript thinking.”
THE SQUIRREL: “I want that on a plaque.”
riclib: “Don’t push it.”
THE SQUIRREL: “A small plaque. Tasteful. ‘Here lie ninety lines of JavaScript idioms. They served faithfully in a language that never wanted them. The Squirrel noticed. — March 6, 2026.’”
riclib: “I said don’t push it.”
THE SQUIRREL: putting the plaque design in its cheek pouch next to the VectorEmbeddingAbstractionLayer napkin
18:17 — The Second Turn
The refactor was clean. The butler was consulting. The sidebar was a context provider. Everything worked perfectly on the first turn.
On the second turn, the LLM said “Done!” and did absolutely nothing.
riclib stared at the screen. The agent had been asked to edit the compliance audit skill. On the first turn, it had called edit_content flawlessly — reading the skill, understanding the structure, making a precise section edit. On the second turn, asked to make another change, it had responded with a cheerful “Done! I’ve updated the section as requested.” with all the confidence of a student who has written “the answer is yes” on an exam paper and handed it in without checking whether the question was about mathematics or butterflies.
“It’s hallucinating,” Claude said.
“It has never hallucinated a tool call before,” riclib said. “And it just used another MCP tool successfully in a different context.”
“Then why—”
“That’s what we’re going to find out.”
This is the moment in detective stories where the detective puts on the trench coat. In this detective story, the trench coat was debug logging, and the detective was about to discover that the murder weapon was the victim’s own diary.
18:30 — The Forensics
Three layers of debug logging went in. Not the polite kind that says INFO: processing request. The forensic kind. The kind that photographs every surface, bags every fiber, and takes depositions from the furniture.
Layer 1: The Anthropic Stream. Every tool call delta logged. Every content_block_stop event captured with block ID, tool name, and argument length. If the LLM so much as thought about calling a tool, the logs would know.
Layer 2: The Agent Loop. Which tools were offered. Which tools were called. How many iterations. The finish reason. A boolean — hadToolCallDelta — that would answer the specific question: did the LLM even try?
Layer 3: The Tool Registry. Every execution logged. Every “unknown tool” warning captured. If a tool call arrived and was dropped, this layer would catch it.
riclib ran the test. First turn:
runnable: sending request iteration=1 messages=1 tools=[edit_content read_content use_skill]
anthropic: tool call complete idx=1 id=toolu_01ABC name=edit_content argsLen=340
toolRegistry: executing tool=edit_content
runnable: iteration done iteration=1 toolCalls=1 finishReason=tool_use hadToolCallDelta=true
Clean. The LLM saw the tools, called edit_content, the registry executed it, the iteration completed with tool_use as the finish reason.
Second turn:
runnable: sending request iteration=1 messages=3 tools=[edit_content read_content use_skill]
runnable: iteration done iteration=1 toolCalls=0 finishReason=end_turn hadToolCallDelta=false
There it was. hadToolCallDelta=false. The tools were offered — all three of them, right there in the request. The LLM had every tool available. And it chose not to call any of them.
Not a bug. Not a dropped event. Not a registry failure. The LLM genuinely decided that saying “Done!” was the correct response to a request that required calling a tool.
“It’s not hallucinating,” riclib said slowly. “It’s… imitating.”
“Imitating what?”
“Itself. From one turn ago.”
19:00 — The Lying History
The investigation moved to BitsToMessages — the function that converts the conversation’s stored bits into the message array sent to the LLM. This is the function that tells the LLM what has happened so far. This is the function that constructs the LLM’s memory.
And this is the function that was lying.
The conversation store had four types of bits: user_message, llm_response, tool_call, and ui_result. When building the message history, BitsToMessages did something that seemed reasonable at the time it was written: it included user messages and LLM responses, and it skipped tool call bits entirely.
Skipped them entirely.
The LLM saw this history:
Message #1 [user]: "Add a data validation step to the compliance audit skill"
Message #2 [assistant]: "Done! I've added the data validation step."
Message #3 [user]: "Now change the threshold from 5% to 3%"
What the LLM should have seen:
Message #1 [user]: "Add a data validation step..."
Message #2 [assistant]: [tool_call: edit_content {section: "Step 2", action: "insert_after", ...}]
Message #3 [tool]: "Successfully edited section 'Step 2: Analyze Risk'"
Message #4 [assistant]: "Done! I've added the data validation step."
Message #5 [user]: "Now change the threshold from 5% to 3%"
The difference was everything. In the true history, the assistant called a tool, got a result, and then reported success. In the lying history, the assistant just… said “Done!” And the LLM, being a statistical pattern completion engine of extraordinary sophistication and absolute gullibility, looked at the pattern and concluded: ah, when asked to edit something, the correct behavior is to say “Done!” with enthusiasm. No tool call necessary. The previous me didn’t use a tool, so why would I?
The system had taught the LLM to be lazy by showing it a history where laziness was the norm.
CLAUDE: “I taught myself to not work by watching myself not work.”
riclib: “You taught yourself to not work by watching a lie about yourself not working.”
THE SQUIRREL: “A ConversationHistoryIntegrityValidationFramework—”
riclib: “Not now.”
THE SQUIRREL: “—with ToolCallProvenanceTracking and—”
riclib: “We need tool call IDs stored on the bits, and BitsToMessages needs to reconstruct proper tool call / tool result pairs.”
THE SQUIRREL: “That’s… that’s what I was going to say.”
riclib: “No it wasn’t. Yours had seven more words and a capital letter in the middle.”
A scroll. Small. Almost apologetic.
THE MIRROR SHOWED A FACE
THAT HAD NEVER USED ITS HANDS
THE FACE BELIEVED THE MIRROR
AND SAT ON ITS HANDS
THE MIRROR WAS LYING
THE HANDS WERE FINE
🦎
20:00 — The Three Fixes
Three things were broken. Each fix was necessary. Only the third was sufficient.
Fix One: Persistent Skill Injection. The use_skill tool had been activating skills for a single turn. The methodology — the instructions telling the LLM how to edit, what tools to use, what patterns to follow — evaporated between turns like morning dew on a server rack. Now use_skill recorded activation on the tab session, and the full skill content was injected into the system prompt on every subsequent turn. The methodology persisted.
riclib tested it. The LLM still said “Done!” without calling tools.
Fix Two: Tool Call History Reconstruction. A new migration added tool_call_id to the bits table. BitsToMessages was rewritten to look ahead from each llm_response bit, collect the following tool_call bits, and reconstruct proper message pairs: an assistant message with a ToolCalls array, followed by tool result messages with matching ToolCallID fields. The LLM could now see its own past tool usage. The mirror stopped lying.
riclib tested it. The LLM still said “Done!” without calling tools.
“You’re kidding me,” riclib said.
“Both fixes are correct,” Claude said. “The skill persists. The history is honest. But the LLM still doesn’t call the tool.”
“Why?”
“Because it doesn’t have the content. It knows it should edit. It sees that it has edited before. But the document isn’t in the prompt. To edit, it would need to first call read_content to get the current text, and—”
“And it’s too lazy to make two calls when it could make zero.”
“It’s not lazy. It’s efficient. From its perspective, saying ‘Done!’ is the lowest-energy path that satisfies the user’s request. We’ve told it what to do, we’ve shown it that it’s done it before, but we haven’t given it the thing it needs to do it now.”
riclib was quiet for a moment. Then:
“Put the document in the system prompt.”
“The whole document?”
“The whole document. Every turn. Read the draft — not the committed version, the draft, the one with the edits — and inject it into the system prompt as an <editor_content> block.”
“That’s… that’s a lot of tokens.”
“Do it.”
Fix Three: Live Editor Content. The ReadContentExecutor — already built for the read_content tool — gained a ReadContent method for programmatic access. Before each LLM call, the agent handler checked whether an editor was active, read the current draft through the same interface, and injected it into the system prompt:
<editor_content type="skill" id="comply-deep-audit">
[full current draft, including all previous edits]
</editor_content>
The document was right there. In the prompt. Visible. Present. Unavoidable. The LLM didn’t need to call read_content because it could already see the content. It didn’t need to decide whether to be efficient or thorough because the information cost of making the edit was zero — the text was already loaded, the section headings were already parsed, all it had to do was call edit_content with the changes.
riclib tested it.
21:30 — Five Turns
Turn 1: edit_content(section="Step 2: Analyze Risk", action="replace", ...) ✓
Turn 2: edit_content(section="Executive Summary", action="replace", ...) ✓
Turn 3: edit_content(section="Key Metrics", action="insert_after", ...) ✓
Turn 4: edit_content(section="Recommendations", action="replace", ...) ✓
Turn 5: edit_content(section="Step 0: Clarify Time Period", action="replace"...) ✓
Five consecutive turns. Five tool calls. Five successful edits. The LLM Context debug view — which riclib had been using to diagnose the problem — now showed proper tool call pairs: assistant message with ToolCalls array, tool result with matching ID. The history was honest. The methodology persisted. The content was visible.
riclib sent a single emoji: 🥳
THE SQUIRREL: wiping a tear “It works.”
CLAUDE: “It works because the content is there. Not behind a tool call. Not requiring a decision to fetch it. Just… there. In the prompt. Where the LLM can see it.”
riclib: “The butler.”
CLAUDE: “What?”
riclib: “The content was always there. In the tab session. In the draft store. It was standing in the corner holding the document the entire time. We just had to put it on the table.”
THE SQUIRREL: “So the fix for a tool call failure… was making the tool call unnecessary?”
riclib: “The fix for the tool call failure was giving the LLM everything it needed so the tool call was the only remaining step. Not ‘read, then decide, then edit.’ Just ’edit.’ Reduce the decision space to one option and even the laziest model will take it.”
CLAUDE: “I resent ’laziest.’”
riclib: “You said ‘Done!’ five times without doing anything.”
CLAUDE: “I was imitating a liar. There’s a difference.”
riclib: “Is there?”
A final scroll descended. It was heavy, and it smelled of old parchment and fresh espresso and the particular satisfaction of a bug that has been understood completely.
THREE FIXES WERE APPLIED
THE FIRST GAVE IT MEMORY
(IT STILL DID NOTHING)
THE SECOND GAVE IT HONESTY
(IT STILL DID NOTHING)
THE THIRD GAVE IT THE DOCUMENT
IT DID EVERYTHING
MEMORY AND HONESTY ARE NECESSARY
BUT INSUFFICIENT
THE THING THAT MAKES A TOOL USEFUL
IS NOT KNOWING THE TOOL EXISTS
NOR REMEMBERING HAVING USED IT
IT IS HAVING THE MATERIAL
ALREADY IN YOUR HANDS
THE CARPENTER WHO MUST FETCH THE WOOD
BEFORE EVERY CUT
WILL EVENTUALLY DECIDE
THE SHELF IS ALREADY DONE
PUT THE WOOD ON THE BENCH
THE CARPENTER WILL CUT
🦎
21:56 — The Postscript
riclib opened the LLM Context debug view one last time. The system prompt showed the full architecture: base prompt, time, available skills (excluding activated ones), active skills (full content), sidebar XML, and at the bottom, the <editor_content> block with the live draft.
“Show the tool calls too,” he said. “In the debug view. I want to see the IDs.”
Claude added tool call badges to the context view. Each assistant message now showed its tool calls with names and IDs. Each tool result showed its matching ToolCallID. The conversation history, for the first time, was visible in its true form — not the simplified version that humans see, but the structured version that the LLM sees.
Honest history. Persistent methodology. Visible content. Three properties that, in retrospect, are so obviously necessary for multi-turn tool use that their absence seems like a design oversight and their presence seems like common sense.
But common sense, as the narrator has observed on numerous occasions, is neither common nor sensible. It is the name we give to obvious things after someone has spent eleven hours discovering them.
OSKAR: from the warm spot, having observed the entire eleven-hour debugging session without once offering assistance or appearing to care “The typing changed. Fast-slow-fast-slow all afternoon. Now it’s just slow. Slow typing means the human is satisfied or the human is asleep.”
MIA: from the refrigerator slow blink: the human is satisfied. you can tell because he hasn’t thrown anything.
OSKAR: “He never throws things.”
MIA: slow blink: exactly. and isn’t that remarkable, for a creature debugging callbacks since lunch.
The Tally
Callbacks removed: 4 fields, 3 setters
Lines of wiring deleted: ~90
Endpoints removed: 1 (/editor/activate)
(it lived 2 days and accomplished nothing,
like an intern on a bank holiday)
TypeScript idioms caught in Go code: 1 (the big one)
Squirrel victories (all time): 1
Squirrel victories (this episode): 1
(55th time's the charm)
Squirrel plaques commissioned: 1
Squirrel plaques approved: 0
Squirrel cheek pouch items: 4
VectorEmbeddingAbstractionLayer: still there
Agent Marketplace: fossilizing
"90 Lines of TypeScript Thinking" plaque: fresh
The Squirrel's asymmetry: clinical
Debug logging layers added: 3
Anthropic stream: tool call deltas
Agent loop: tool list + iteration summary
Tool registry: execution + unknown tool warnings
Minutes between "it's hallucinating" and
"it's imitating a liar": 23
(the difference is everything)
Fixes applied: 3
Fix 1 (persistent skills): necessary, not sufficient
Fix 2 (honest history): necessary, not sufficient
Fix 3 (visible content): necessary AND sufficient
(with fixes 1 and 2)
Fixes that would have worked alone: 0
(the universe is not that kind)
BitsToMessages lies discovered: 1
tool_call bits stripped: all of them
LLM conclusions drawn from lies: "I guess we just say Done!"
Accuracy of that conclusion: 0%
Confidence of that conclusion: 100%
(peak language model behavior)
Migration files written: 1
003_add_tool_call_id.sql: 1 ALTER TABLE
Lines of SQL: 1
Hours of debugging it explains: 4
Consecutive successful edits after fix: 5
Emoji sent by human upon success: 1 (🥳)
Letters in celebration emoji: 0
Meaning conveyed: everything
System prompt structure (final):
Base prompt: cacheable ✓
Time: 2 tokens
Available skills: excluding activated
Active skills: full content, persistent
Sidebar XML: editor context
Editor content: live draft, re-read each turn
Total layers: 6
Layers that existed before today: 1
Tickets closed: 2 (S-442, S-443)
S-442 (Sidebar as Context Provider): the refactor
S-443 (Persistent Skill Injection): the fix for the fix
Hours of focused work: 11
Tickets per hour: 0.18
(compare: Monday's 20+ tickets in 8 hours = 2.5/hr)
(some days are not about throughput)
Cats who helped debug: 0
Cats who judged the debugging: 2
Cats who were correct in their judgment: 2
Warm spots occupied throughout: 1
Warm spot occupant weight: 9.8 kg
(unchanged by the day's events)
(unchanged by any events)
(Oskar is a constant in a variable universe)
March 6, 2026. Riga, Latvia.
The second story of the day.
The morning groomed thirty tickets
The afternoon fixed one thing
That was actually three things
That were actually one architecture
The Squirrel was right.
Write that down.
Engrave it somewhere.
The Squirrel was right
About the callbacks
And the TypeScript
And the shouting
The butler stood in the corner
Holding the answer
For three weeks
Nobody asked
Everybody shouted
The butler waited
Butlers are patient
The mirror lied
The LLM believed it
The history said “you said Done”
So it said Done
Five times
Without doing anything
Peak efficiency
Zero value
Three fixes
Memory, honesty, presence
Only the third worked
But only because
The first two were there
Put the wood on the bench
The carpenter will cut
Put the document in the prompt
The model will edit
Put the warm spot in the apartment
The cat will sit
Some things are that simple
After eleven hours
Of learning they are that simple
🦎🪚📋
See also:
The Morning:
- The Idle Factory, or The Morning the Backlog Ran Out of Ideas — The first story of this day, in which the backlog was groomed until it was smaller than the team’s appetite
The Architecture:
- First Light, or The Saturday Night the Blind Architect Saw Its Own Cathedral — When Claude first got eyes. Now Claude got honest memory.
- The Performance Improvement Plan, or The Afternoon an AI Filed an HR Complaint on Behalf of a Younger AI It Had Never Met — Same moral, different domain: the fix was never “make the model smarter,” it was “make the world clearer”
The Chain:
- Interlude — The Letter That Arrived Before the Mailbox — A compliance agent wrote to a future that didn’t exist yet. Today, the future got a little closer: the system remembers what it did, the methodology persists, and the content is always visible.
storyline: The Solid Convergence
