Two LLM UI Patterns That Aren't Chat
Intro
Chat is still the default LLM interface, and for most cases that's fine. Agentic harnesses are still built around a single linear conversation at their core. Some LLM tasks are better represented as structured context than as messages. This post looks at two patterns: comparison as a table, and exploration as a tree.
I've included my explorations here, along with some live example apps I put together using shelley on exe.dev.
Comparison

- Link: Comparitable
Asking an LLM to compare things in chat quickly becomes tedious. At first you get a decent table, but as you ask follow-up questions, the useful information gets split across multiple answers, or the LLM tries to redraw the table every time. Adding another item makes the regeneration problem worse.
The table is already the thing you want. The items are rows, the questions are columns, and the answers are cells.
I explored a hybrid chat/table interface where new questions do not become new messages in a linear stream. Instead, they create new columns. Added items create new rows.
In practice it feels like chatting with a spreadsheet.
Building the Table
After entering a topic, the app searches for relevant items, fetches their pages, and fills the first version of the table. For "ultralight 1-person tents" that might look like:
+----------------------+----------+-------+-------------------+
| Item | Weight | Price | Wall Type |
+----------------------+----------+-------+-------------------+
| Zpacks Plex Solo | 405 g | $599 | Single-wall DCF |
| Big Agnes Fly Creek | 879 g | $350 | Double-wall |
| Tarptent ProTrail Li | 425 g | $399 | Single-wall DCF |
| Nemo Hornet Elite | 765 g | $450 | Double-wall OSMO |
+----------------------+----------+-------+-------------------+
The app currently uses Kagi search to gather items, but the same approach would work well embedded in a shopping site, marketplace, internal product database, recruiting tool, or anywhere else the rows already exist.
The initial columns are also generated from the first search results. This is a small detail, but it matters. The app does not need to start from a blank table. It can look at the retrieved items and choose a few useful dimensions to kick things off.
Questions Become Columns
Once the table exists, type a question and a new column appears. The table stays put. The question adds one comparison dimension to the same object rather than producing another answer below the last one.
The model call itself is ordinary: given these item summaries, answer this question for each row. What changes is that the result has somewhere specific to land. The structure it lands in is already the thing you are trying to build.
Creating the LLM call as a tool call which specifically returns an answer for each row also fits the current generation of models well.
Prompting Example
The user prompt looks roughly like:
And the tool definition:
{
"name": "fill_column",
"description": "Answer a comparison question for each item in the table.",
"input_schema": {
"type": "object",
"properties": {
"answers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"row_id": { "type": "string" },
"value": { "type": "string" }
},
"required": ["row_id", "value"]
}
}
},
"required": ["answers"]
}
}
The important constraint is simple: return exactly one cell value for each row id. The model is not being asked to write a new answer. It is being asked to fill a specific part of the table.
Not Just Specs

Because a model is filling the cells, the columns are not limited to structured specs.
Translation comes almost for free. If an item's page is in Japanese, and I want the table in English, the cell can just be filled in English. I do not need a separate translation mode in the UI.
Unit normalization is similar. If I ask Weight (g)? the model can usually get everything into the same unit, assuming the source pages have enough information.
Soft judgement calls also work better than I expected. "Is this good for a beginner?" is not a product spec, but it is still a useful comparison question. It is not perfectly objective, but most real buying decisions are not perfectly objective either.
For questions that need fresher context, the app can search again before filling the column. "What does Reddit think about this?" becomes a column like any other, backed by live search rather than the cached product page.
The product comparison case is the obvious one, but the same interaction pattern could apply anywhere you are evaluating a set of things against a shared set of questions.
Decomposition

- Link: Breakdowner
Some tasks require exploration of many different branches of context. During the course of what begins as a linear topic, small sub-topics arise that each warrant their own focused exploration. In a single chat interface these bleed together and, in the worst case, poison the context and prevent going deeper on any one thread.
The natural shape for this kind of work is a tree, where each branch inherits its parent context but is independent of its siblings. It's one I've used before when working with gptel in Emacs. An option allows you to limit the conversation within an org-mode heading.
I explored a small outliner/task planner, where each node can be expanded by an LLM. Rather than one running context, each branch gets its own focused expansion. You go deep where you need to and leave the rest alone.
Exploring the Tree
Each node has an expand button. Clicking it sends the current heading and some surrounding context to the model. The model replies with one of three shapes.
A breakdown is a handful of concrete sub-items, inserted as children.
A question means one missing detail would change the answer, so the model asks instead of guessing. For "Choose model hosting for small finetuned model", rather than generating a generic breakdown, the model asks about parameter size and expected request volume, with suggested choices to pick from or a free-text fallback.

An options response covers mutually exclusive paths. Picking one replaces the current node label while the original goal stays in the parent chain for context.
The Prompt
Breakdowner sends one user message to the model with a single available tool: search. The prompt asks for exactly one JSON object in one of three shapes:
{"type": "breakdown", "items": ["..."]}
{"type": "question", "question": "...", "choices": ["..."]}
{"type": "options", "options": ["..."]}
The distinction is: breakdown for parallel concrete next moves, question when missing info would change the answer, and options for mutually exclusive paths where picking one replaces the node.
The prompt pushes hard against generic planning language. NOT: "Research options", "Set up environment", "Create plan", "Test and iterate", "Evaluate". Positive examples given to the model:
- Pick Stripe vs Paddle
- Find 10 people who complained about X on Reddit
- Compare prices at 3 stores vs delivery
A vague child node just creates more work for the next expansion. A concrete child node can be acted on, searched, delegated, or broken down again.
What Gets Sent
The model sees the current heading, the parent chain, and any prior answers collected from question nodes earlier in the branch. No full global tree, just the focused context around the node being expanded:
The parent chain keeps a deep node connected to the top-level goal. Prior answers mean the model does not forget that the budget is tight, or that this needs to happen in Tokyo, or that the user has never done the thing before.
Sibling branches stay out. That is the bit I like most about this pattern. Context is selected by the tree structure instead of being whatever happened to appear earlier in the transcript.
Outro
Both of these tools are small and the model work inside them is ordinary. What they are really about is the context: how it gets built, how it gets used, and whether the UI is shaped to support that.
Chat has established itself as the default interface, and that is fine. But most LLM tooling still treats context as an afterthought: a transcript that grows in one direction, with the UI doing very little to shape it. Worse, that transcript is mostly hidden from the user. They type, they get an answer, and what the model actually saw stays opaque.
The structure around the model call, and the UI that makes a particular kind of context feel natural and visible, is where I think a lot of the interesting and mostly unexplored design work lives.