Plenty of what we ship has no chat UI at all — the domain is the product, and the surface is bespoke. Pouch's home view, attic's quiet backup pipeline, the Chiu Chang math games: each is shaped around a thing it's doing, not around a conversation. Those don't share much chassis with each other, and that's fine.
The corner this post is about is different. Several of the more recent things — a voice room, a sales-call companion, an internal research console, a trip companion's planner view — keep showing up with the same bones. Chat-driven main column. Tools that emit either text or a structured widget. A focus panel for when one of those widgets is worth a closer look. A small history that survives a reload. Different domain, same scaffold. We kept rebuilding it.
liugent, the bench
liugent (the name is provisional, the thing is brewing — liu as in 流, flow) is what we factored out. A small Go server with a registry of tools. A pnpm UI on top of motif. A WebSocket bus between them. The interaction model is well-understood by now: you type, a tool runs, it can emit text into the conversation OR a structured block, structured blocks get a widget, widgets can carry action buttons that send commands back. None of that is novel. The novelty, if there is any, is in not building it again.
The name points at where we want this to go. A lot of AI-shaped software hides the flow — the model decides, the user reacts to the output. We're more interested in the opposite direction: making the flow visible enough to steer, with the agent contributing where it's clearly better than the human and the human staying in the loop everywhere else. The bench is the part of that we can build now; the steerable flow is the part we'll be growing into.
What we wanted to make easy was the domain part. A new app should be a plugin — a package that registers tools, a handful of widget kinds the UI knows how to render, and that's it. No chat framework decisions, no canvas state machine, no WebSocket reconnect over Mac sleep, no notebook persistence, no pin/rail/chip UI. Those live in the bench.
Internal use first
We're testing this idea where we always do — by using it ourselves first. The first liugent plugin is a workbench for one of us who follows the Taiwanese market: portfolios of options and warrants, a playbook engine that scores each leg against a configurable rule set, a live-quote bridge to a broker, a notebook that survives the laptop going to sleep. The plugin owns the domain math. The bench owns everything else.
It's been a useful forcing function. The chassis couldn't get too clever or too rigid because a real workflow was running on it — open during TWSE day session, again through the night session, again at the US close. A few features got pulled in because we needed them (pin as shortcut, scroll-restore on canvas close, auto-reconnect with backoff). A few got pushed out because the plugin could do them better — most of the domain math, which has no business near the chassis.
What a tool emits
Here's roughly what the playbook tool returns, flattened to text since we don't have a screencast ready. In the live UI it's a widget, not a code block; in the journal it'll have to do.
trade-july · 1 suggestion
Capital NT$450,000 · Cash NT$352,250 (78%)
TP tiers (auto-mode) stocks 20/40/80 · options 50/100/200
Position
L2 45000C-2026-07 qty 1 ×50 entry 1,370 mark 1,795 +31% DTE 37d
L1 39000P-2026-07 qty 1 ×50 entry 585 mark 530 -9% DTE 37d
Suggestion
HOLD · no triggers
Distance to tier 1 on L2: +14% on call mark ≈ TXF +1.1%
That output is the plugin's. Everything around it — the composer that sent the command, the notebook that holds the chronological history, the canvas that you can open to read it closely, the chip you can pin to come back to this exact slot when the next refresh lands — is the bench. Click "refresh" on the widget and the bench re-emits the same command through the bus; the new block arrives, the canvas swaps if it's open, the chip stays. The plugin doesn't know any of that happened.
What we're watching
Two things will tell us whether we drew the line in the right place.
The first is the plugin/bench boundary itself. We've already had to move a couple of helpers across the line. The shape of that motion — what wanted to live where — tells us whether the boundary is real or imagined. The current cut feels right. We'd rather be wrong twice than not check.
The second is whether a second plugin will be as cheap to add as we're betting. The first plugin always benefits from incidental decisions made for it. The second plugin pays the bill. When we add one we'll know whether the bench was worth factoring out, or whether we just relocated the duplication.
Where this leaves the journal
When specific apps are right for a public release, we'll build them specifically — liugent's job is to not be in the way during the years we're not. For now it's a quiet thing in a corner of the workshop. The plugin is doing real work daily; the chassis is being held to a higher standard by being used every session. That's usually how we know we've got the right thing brewing.
The rest of the workshop — pouch, attic, mytraces, the Chiu Chang games — keeps moving in parallel; they don't share liugent's chassis and most of them don't need to. This was just a note from the corner we hadn't introduced yet.
← All journal posts