Taking apart the chat interface
A chat window is a terminal with one command. These are the other commands, and what each one changes about the interface.
Motivation
A chat interface has a simple shape underneath. You type, a system runs, text comes back. Type again and the whole conversation so far goes with your new message, because the model keeps nothing between calls. The scroll of alternating bubbles is a rendering choice on top of that loop.
Every other interface built on that shape has more than one thing you can do.
- Terminal. Commands. Output is a file, a process, or a value you hand to the next command.
- Notebook. Cells. Output stays on screen and can be referenced later.
- Chat. Messages. Output is more text. One command, and its output is prose.
So in January I worked out what a chat interface would look like with the rest of the vocabulary.
The substitutions
This is not a chat. The user issues commands in natural language, the system parses the command, something runs, and you watch it run. Each replacement below is a design decision, not a rename.
| Chat | Console | What changes |
|---|---|---|
| chat | session | a transcript becomes an environment with contents |
| message | cell | finished text becomes a function run that can still be running |
| file | object | opaque bytes become data with its methods attached |
| you write the shell script | the agent writes it | and you can read what it wrote |
Sessions. A transcript accumulates only text. An environment has contents: it starts empty, the way a coding agent starts in an empty folder, and you load things in or create them as you go.
Cells. A message has already finished. A cell is a function run with a view attached, so it can still be running. Ask it to ping you every five minutes and the cell keeps a green dot and stays alive while you do other things. Background processes stop being exotic and become the ordinary case of a cell that has not finished.
Objects. The substitution I care most about:
- A REPL gives you objects, and they evaporate when the session ends.
- A filesystem gives you persistence, and a file is opaque: you can read the bytes, and nothing about the file says what you may do with it.
I wanted persistence with the operations attached: methods attached to data that live in the filesystem, so the thing that survives a session is the object and its interface together. The methods are self-describing, so whatever looks at the object can find out what can be done to it without being told separately. Predefined types would be nice and are not required, because methods can be attached to data dynamically. It is a REPL, constrained.
Put together: object-oriented chat.
The orchestration language
If the agent writes the orchestration, something decides what it writes, which is a language design question rather than an interface question. I decided it should generate Lisp.
Code and data have the same shape in Lisp. A program is a list, and a list is something a program can build, take apart, and hand around. When the writer is itself a program that stops being an elegance and becomes a convenience: the output is already a data structure, so you can show it, store it in the session, pass it to another cell, or rewrite it, without parsing anything back out of a string.
Embed one of the small JavaScript Lisp interpreters, or write one. I wrote one, and the reason on the page is control rather than any capability the embeddable ones lack. What is being evaluated is not general-purpose code, it is operations over the objects in the environment, which is a small enough target that owning the evaluator costs little.
Re-deriving the motivation
With the interface reframed and the language chosen, the design had run ahead of its own justification, so the next thing written down was that this was going too deep and we needed more clarity, and that we should think about why we even want the console at all.
Nine reasons, written from scratch, in one go:
- It is not chat but works like chat.
- You do not think in terms of agents, you think in terms of objects and functions. In fact an agent can just be an object in the environment.
- User to agent becomes user to environment.
- A richer visualization paradigm than generated UI or tool messages, because each cell is a function run with a view, and that function can be running in the background.
- Each object becomes a primitive for your AI system.
- Custom renderers and editors for objects, which makes it a digital workspace.
- More abstract than a filesystem, since a filesystem can itself be an object.
- A template other projects can start from instead of a chatbot, where you define the object types.
- Easier to use agentic systems while still feeling free and composable rather than a rigid UI.
I would do that again. A run of decisions each defensible on its own terms still accumulates into a direction nobody chose, and the failure is silent because every step passed its own check. Asking why the thing should exist is cheap at that point and expensive later, and the list either produces reasons or it does not.
User to environment
Most of the nine are features. Reason three is not, and it is the only one still doing work in what I build now.
- Chat framing. Someone is on the other side and you negotiate: you explain, they misunderstand, you rephrase.
- Environment framing. No someone. A place with things in it, the agent one more thing in that place, and your commands act on the place.
The agent stops being your counterpart and becomes an instrument operating on shared state you can also see and touch. Smaller claim than object-oriented chat, and the durable one.