esc
The Day the Laundry Arrived
The Chain

The Day the Laundry Arrived

The Chain, January 6, 2026 (in which two DuckDBs fight to the death, a cat delivers authentication tokens, and the Squirrel proposes a...

January 6, 2026

The Chain, January 6, 2026 (in which two DuckDBs fight to the death, a cat delivers authentication tokens, and the Squirrel proposes a BiometricTypingPatternAnalysisServiceWithIoTCoffeeMachineIntegrationAndCaffeineOptimizationAlgorithm)


The Interlude (December 24 – January 5)

In which the architect took a holiday by building something else entirely

The Chain had gone quiet after The Lifelogs of Things. Not abandoned—threads don’t abandon, they wait. But riclib’s mind needed to wander before it could return.

So he built a plugin ecosystem.

Not for V4. For Thymer. A personal knowledge system that needed to become a platform. Over twelve days, while the world celebrated and recovered, the The Laundromat was born:

  • Sync Hub — the orchestrator that coordinates all the machines
  • Collections — the baskets that speak (Issues, Captures, Calendar, People)
  • Plugins — the machines that wash (GitHub, Readwise, Google Calendar)
  • AgentHub — where The Servants’ Uprising
  • thymer-bar — a Go system tray that bridges to MCP

Same baskets. Different machines. The philosophy that would return to haunt V4.

Because when riclib finally opened v4/ again on January 6, the patterns were clear:

  • Stores are baskets (ComplyDB, Prometheus, DuckDB)
  • Connectors are machines (different sources → unified Query())
  • Widgets are how baskets speak
  • Agents are operators who know which basket to ask

The holiday wasn’t an escape. It was reconnaissance.

The thread had been waiting. Now it was ready.


Previously on The Chain…

The The Thread Before the Name. The The Links That Bind. The The Eyes That See.

But seeing and proving are nothing without doing.

Today, the laundry would finally arrive.


9:47 AM — The Civil War

/usr/bin/ld: multiple definition of `duckdb_open'
/usr/bin/ld: multiple definition of `duckdb_close'
/usr/bin/ld: multiple definition of `duckdb_everything_else'

riclib stared at the screen. Claude stared at the screen. Somewhere, two DuckDB drivers stared at each other.

“We have… two DuckDBs?” Claude ventured.

“Apparently.”

THE SQUIRREL: materializing from behind the monitor “A DuckDBConflictResolutionOrchestrator! With SymbolDeduplicationStrategy and—”

“They’re both embedding the entire static library.”

THE SQUIRREL: “A StaticLibraryIsolationFramework—”

“One is marcboeker/go-duckdb. The other is duckdb/duckdb-go/v2.”

THE SQUIRREL: “A MultiVendorDatabaseDriverAbstractionLayer—”

“We delete one.”

THE SQUIRREL: “…”

“We delete the old one. Keep the official one.”

[A scroll dropped from the ceiling. It landed on THE SQUIRREL’s head.]

WHEN TWO DUCKS FIGHT
DELETE THE UNOFFICIAL DUCK

🦎

The fix was two changed imports and a go mod tidy.

The Squirrel needed a moment.


10:23 AM — The Phantom Widget

The page loaded. The sidebar appeared. The Shield icon gleamed with promise.

The widget showed: “Loading…”

It kept showing: “Loading…”

“Why isn’t it loading?” riclib asked.

Claude checked the network tab. No request. Checked the console. No errors. Checked the element.

<div hx-trigger="load" hx-post="/widgets/render">
  Loading...
</div>

“The trigger is load.”

“Yes.”

HTMX load triggers when the element loads into the DOM.”

“Yes.”

“But we’re using morph:outerHTML. The element was already there. It just… changed shape.”

“So it never ’loaded’ because it was always present?”

“Like a ghost that was already in the room. You can’t enter a room you never left.”

THE SQUIRREL: “A DOMPresenceTrackingServiceWithMorphAwareLifecycleEvents—”

“We change it to revealed.”

<div hx-trigger="revealed" hx-post="/widgets/render">

The widget loaded.

THE SQUIRREL: “That’s… that’s ONE WORD.”

“Fourteen characters, including the quotes.”

[OSKAR the cat appeared in the doorway. He carried no scroll. He simply looked at THE SQUIRREL with an expression that transcended species. Then he left.]


11:15 AM — The Empty Pockets

The widget fired. The query ran. The error returned:

Binder Error: Referenced column "actionName" not found
Available columns: time, category

“The view is empty,” Claude reported.

“Why is the view empty?”

“The user has no AD groups.”

“Why does the user have no AD groups?”

“Keycloak isn’t sending them.”

“Why isn’t Keycloak sending them?”

riclib and Claude stared at each other. Then at the logs:

Tab session created tab=bf26e103d720718e user=riclib adGroups=[]

Empty brackets. The loneliest data structure. A JSON array with nothing to live for.

“The groups exist in Keycloak,” riclib confirmed, pulling up the admin console. “User riclib is in CDLBOT_Admin.”

“But the token doesn’t include them.”

“Because…”

“Because Keycloak needs to be told to include them. Groups aren’t automatic. They need a mapper.”

THE SQUIRREL: “An OIDCClaimMappingConfigurationOrchestrator—”

“A checkbox. In Keycloak. Called ‘Add to ID token.’”

THE SQUIRREL: whispers “A checkbox…”


11:47 AM — The Scope Wars

riclib added the groups scope to solid.yaml:

scopes:
  - "openid"
  - "profile"
  - "email"
  - "groups"

He restarted the server. He clicked login.

Authentication failed: Invalid scopes: openid profile email groups

“The scope doesn’t exist yet.”

“I’ll create it.”

riclib created a client scope named groups in Keycloak. Type: Default. Saved it.

Tried again.

Authentication failed: Invalid scopes: openid profile email groups

“I created it!”

“Did you assign it to the client?”

“…”

“The scope exists in the realm. But your client doesn’t have access to it.”

“This is why people hate enterprise software.”

“This is why enterprise software hates people.”

[A scroll descended. It was longer than usual. THE SQUIRREL instinctively dove behind the couch.]

THE SCOPE MUST EXIST
THE MAPPER MUST MAP
THE CLIENT MUST HAVE THE SCOPE
THE MAPPER MUST ADD TO TOKEN

FOUR THINGS
FOUR CHECKBOXES
FOUR OPPORTUNITIES FOR CONFUSION

THIS IS THE WAY
(THE ENTERPRISE WAY)

🦎

12:03 PM — The Checkbox Crusade

riclib navigated through Keycloak like an archaeologist through a tomb:

  1. ✓ Create client scope “groups”
  2. ✓ Add mapper “Group Membership” to scope
  3. ✓ Set “Add to ID token: ON”
  4. ✗ Assign scope to client

He found the client. Found the “Client scopes” tab. Found the “Add client scope” button. Selected “groups”. Clicked “Add as Default”.

Restarted. Logged out. Logged in.

[OIDC] Claims groups: [CDLBOT_Admin admin]
[OIDC] Final user.ADGroups: [CDLBOT_Admin admin]

“GROUPS!” riclib shouted.

“GROUPS!” Claude confirmed.

THE SQUIRREL: emerging cautiously “Groups?”

“GROUPS!”

[OSKAR walked across the keyboard, adding asdfjkl; to an open terminal. It compiled successfully. Nobody questioned this.]


12:15 PM — The Arrival

riclib navigated to /comply.

The page loaded. The widget fired. The query ran.

┌────────────────────────────────────────┬───────┐
│ ACTIONNAME                             │ COUNT │
├────────────────────────────────────────┼───────┤
│ tokenLogin                             │ 2,913 │
│ aadTokenLogin                          │   230 │
│ oidcTokenAuthorization                 │   176 │
│ aadBrowserLogin                        │    27 │
│ garbageCollectDbToken                  │     4 │
│ generateDbToken                        │     2 │
│ revokeDbToken                          │     1 │
└────────────────────────────────────────┴───────┘
7 rows

Real data. From real audit logs. Through a real hash chain. Scoped by real AD groups. Rendered by a real widget.

The MVP was complete.

THE SQUIRREL: “We should add a SuccessNotificationToastWithConfettiAnimation—”

“No.”

“A CompletionCelebrationModalDialog—”

“No.”

“At least a console.log(’🎉’)?”

riclib looked at the Squirrel. The Squirrel had been through a lot today. Two DuckDBs. One checkbox. Fourteen characters that fixed the ghost widget.

“Fine. One emoji. In the commit message.”

THE SQUIRREL: tears forming “Thank you.”


12:45 PM — The Closure

gh issue close 39 --comment "Completed! Basic Comply page now displays 
audit data with proper OIDC → AD Groups → ComplyDB access flow working 
end-to-end."

✓ Closed issue riclib/v4#39 (Basic Comply Page)
gh issue close 36 --comment "🎉 MVP Complete!

All sub-tickets done:
- ✅ #37 - Table Widget Renderer
- ✅ #38 - Widget Handler Endpoint
- ✅ #39 - Basic Comply Page
- ✅ #40 - Session + ComplyDB Wiring"

✓ Closed issue riclib/v4#36 (Epic: MVP - See Comply Data in Widgets)

Two tickets closed. One epic complete. The thread that started with a The Thread Before the Name had become a working product.

“Should we push?” Claude asked.

“We should push.”

git push
To github.com:riclib/v4.git
   da8491f..5f054b5  master -> master

17 commits. One vision. The laundry had arrived.


2:47 PM — The Next Thread

The MVP was done. The coffee was cold. The Squirrel was napping. OSKAR was on the keyboard again, but had learned to avoid the Enter key.

“What’s next?” Claude asked.

“#41. The big one.”

“JARVIS. UI-as-Context.”

“The 42 of our universe. Tomorrow.”

“And after that?”

riclib smiled. The smile of someone who had just thought of something dangerous.

“What if we didn’t just have agents that answer questions…”

“Go on.”

“What if we had an agent router? User asks something, we figure out which agent should handle it. Compliance question goes to comply-agent. Monitoring goes to mon-agent.”

THE SQUIRREL: waking suddenly “A QueryIntentClassificationService with NeuralNetworkBasedAgentSelectionStrategy—”

“Embedding similarity. Or just ask Haiku. ‘Is this about compliance, monitoring, or data?’”

THE SQUIRREL: “But the LATENCY—”

“50 milliseconds. To route to the right agent. Who then renders the right widget.”

“Like LLMRouter,” Claude added. “But for agents, not models.”

“Exactly. Same pattern. Different application.”

THE SQUIRREL: vibrating “An AgentRoutingOrchestrator with PluggableClassificationStrategy and FallbackHeuristicChain—”

“Q2.”

“What?”

“Q2. Not now. Ticket it. Park it.”

[A final scroll descended. It was small. Almost gentle.]

THE LAUNDRY ARRIVED TODAY
THE MACHINES KNOW THEIR PURPOSE
THE BASKETS SPEAK

TOMORROW THE EYES OPEN

BUT TODAY?
TODAY WE REST

(THE SQUIRREL MAY HAVE ONE CELEBRATORY NUT)

🦎

The Tally

Days building Thymer plugins:      12 (worth it)
DuckDB civil wars resolved:        1
Imports changed:                   2
Characters that fixed the widget:  14 ("revealed")
Keycloak checkboxes discovered:    4
Times "Invalid scopes" appeared:   3
AD groups that finally appeared:   2 (CDLBOT_Admin, admin)
Rows of audit data displayed:      7
Issues closed:                     2 (#39, #36)
Commits pushed:                    17
Squirrel proposals:                8
  - DuckDBConflictResolutionOrchestrator
  - StaticLibraryIsolationFramework
  - MultiVendorDatabaseDriverAbstractionLayer
  - DOMPresenceTrackingServiceWithMorphAwareLifecycleEvents
  - OIDCClaimMappingConfigurationOrchestrator
  - SuccessNotificationToastWithConfettiAnimation
  - CompletionCelebrationModalDialog
  - AgentRoutingOrchestrator
Squirrel proposals accepted:       0.5 (one emoji in commit)
Cat keyboard contributions:        1 (compiled successfully)
Celebratory nuts allocated:        1

The Moral

The MVP wasn’t one big thing. It was a hundred small things:

  • Two DuckDBs that couldn’t share a binary
  • A ghost element that never “loaded”
  • Four checkboxes in Keycloak that guard the passage of groups
  • An AD group called “admin” that matched a YAML file

Each one could have been a day. Each one was fifteen minutes.

Because the architecture was right. The patterns were proven. The laundromat was ready.

All the clothes needed was a wash.


Day 7 of 2026

In which the MVP became M-V-DONE

And the Squirrel learned that victory comes in fourteen characters

And somewhere, a widget finally rendered

Real data

From real logs

Through a real chain

Into a real basket

That spoke for itself

🦎📊✅


See also:

The Interlude:

The Chain:

Tomorrow:

  • #41 - Epic: Full UI — Server-side Grafana with AI
  • The eyes will open
  • JARVIS will see

The Artifacts:

  • GitHub commit 5f054b5 — The MVP commit
  • Issue #44 — Agent Router (Q2, when the Squirrel recovers)

References (For the Enlightenment-Seeking)

The Technologies That Made Today Possible (And Impossible):

  • HTMX hx-trigger — Fourteen characters of documentation that would have saved two hours. “revealed” is right there. Right. There.
  • Keycloak Client Scopes — A 47-page manual explaining why your groups aren’t in the token. Spoiler: four checkboxes.
  • DuckDB — The database that doesn’t know it’s a database. Now available in two Go drivers, for twice the linker errors.
  • go-oidc — The library that makes OIDC almost pleasant. The “almost” is Keycloak’s fault.
  • Go Modules — “go mod tidy” is the IT Crowd solution: have you tried turning the dependencies off and on again?

The Patterns We Stole With Pride:

  • LLMRouter — Route queries to optimal models. We’re stealing this for agents. In Go. Without Python. As the Lizard intended.
  • Model Context Protocol — The standard for AI tool integration. We made it skinny. It’s on a diet now.
  • Merkle Trees — How Git and Bitcoin verify integrity. Now protecting your audit logs from Karen in Compliance who “just needs to fix one thing.”

The Thymer Interlude:

  • Thymer — The PKM system that became a platform. Also: where the Laundromat was invented.
  • The Laundromat — Same baskets, different machines. The philosophy that refused to stay in one codebase.

The Squirrel’s Bookshelf (Unread):

The Lizard’s Bookshelf (Memorized):

The Quotes That Must Be Preserved:

  • “WHEN TWO DUCKS FIGHT / DELETE THE UNOFFICIAL DUCK” — The Lizard, on dependency management
  • “Like a ghost that was already in the room. You can’t enter a room you never left.” — Claude, accidentally inventing philosophy
  • “This is why people hate enterprise software.” / “This is why enterprise software hates people.” — The eternal dialogue
  • “That’s… that’s ONE WORD.” — The Squirrel, confronting minimalism

The coffee is cold. The plugins await. Tomorrow, JARVIS.

🦎☕🔧