<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>LaunchX · Qijun Han</title><link>https://sergiudm.github.io/tags/launchx/</link><description>Qijun Han, first-year PhD student in the VLAA Lab at UC Santa Cruz, working on computer-use agents and multimodal models.</description><generator>Hugo</generator><language>en-US</language><managingEditor>Qijun Han</managingEditor><lastBuildDate>Wed, 11 Nov 2026 09:00:00 +0000</lastBuildDate><atom:link href="https://sergiudm.github.io/tags/launchx/index.xml" rel="self" type="application/rss+xml"/><item><title>The Bottleneck Is Now the Interface</title><link>https://sergiudm.github.io/p/software-in-the-next-3-years/</link><pubDate>Wed, 11 Nov 2026 09:00:00 +0000</pubDate><guid>https://sergiudm.github.io/p/software-in-the-next-3-years/</guid><description>&lt;p class="lead"&gt;Three years ago an LLM could only chat. Today an agent can plan, call tools and work for hours, and nearly all of that progress came from two places: better models, and better harnesses around them. The way we talk to agents has not moved. It is still a chat session. I think that is now the weakest part of the whole system, and that the next three years of software should go into fixing it. This post is the argument, with pictures, and a look at what we are building toward at &lt;a href="https://github.com/AutoPhd-org" rel="noopener"&gt;AutoPhD&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="from-chatbots-to-agents"&gt;From chatbots to agents&lt;a class="anchor" href="#from-chatbots-to-agents" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;First we had LLMs, and an LLM did one thing: you sent it text and it sent text back. Put a text box in front of it and you have a chatbot. A chatbot can explain how to rename a hundred files. It cannot rename them.&lt;/p&gt;&#10;&lt;p&gt;We wanted it to act. So we let the model call functions, gave it the results, and let it go round again until the job was done. The code that does this (the loop, the tools, the memory, the sandbox, the permission prompts) is the &lt;strong&gt;harness&lt;/strong&gt;, and a model inside a harness is what we now call an agent.&lt;/p&gt;&#10;&lt;p&gt;From 2023 to 2026 the harness is where most of the invention happened: function calling, tool protocols such as MCP, computer use, coding agents that live in a terminal, skills, subagents, runs that go on for hours. The models improved underneath all of it.&lt;/p&gt;&#10;&lt;p&gt;In 2023, when an agent failed, it was nearly always the agent&amp;rsquo;s own fault. It lost the plan, repeated an action that had already failed, or announced success too early.&lt;span class="sidenote-wrap"&gt;&lt;label for="sn-0" class="sidenote-ref" aria-label="Show note"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-0" class="sidenote-toggle"&gt;&lt;span class="sidenote" role="note"&gt;The last two are what my own paper, &lt;a href="https://arxiv.org/abs/2604.21375" rel="noopener"&gt;VLAA-GUI&lt;/a&gt;, is about: a verifier that decides when to stop, and a loop breaker that decides when to change strategy.&lt;/span&gt;&lt;/span&gt; Everything around the agent was good enough, because the agent could not have used anything better.&lt;/p&gt;&#10;&lt;figure class="figure wide"&gt;&lt;img src="https://sergiudm.github.io/p/software-in-the-next-3-years/pipeline-then.svg" alt="2023: you write a prompt and read an answer, the agent executes a tool call or two on software and explores what the tools return. The agent is marked as the bottleneck: it lost the plan, repeated itself and stopped too early." loading="lazy" decoding="async" class="only-light" data-zoom="/p/software-in-the-next-3-years/pipeline-then.svg"&gt;&lt;img src="https://sergiudm.github.io/p/software-in-the-next-3-years/pipeline-then-dark.svg" alt="2023: you write a prompt and read an answer, the agent executes a tool call or two on software and explores what the tools return. The agent is marked as the bottleneck: it lost the plan, repeated itself and stopped too early." loading="lazy" decoding="async" class="only-dark" data-zoom="/p/software-in-the-next-3-years/pipeline-then-dark.svg"&gt;&lt;figcaption&gt;&lt;strong&gt;2023.&lt;/strong&gt; You write a prompt and read an answer; the agent makes a tool call or two. The narrowest part of the pipeline is the agent itself.&lt;/figcaption&gt;&lt;/figure&gt;&#10;&#10;&lt;h2 id="the-bottleneck-moved"&gt;The bottleneck moved&lt;a class="anchor" href="#the-bottleneck-moved" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;That stopped being true. In the VLAA-GUI experiments, three of the five backbone models we tried finished more OSWorld tasks than the human baseline (72.4%), and the best reached 77.5%. On that benchmark, an agent driving a real desktop with a mouse and a keyboard now does at least as well as a person.&lt;/p&gt;&#10;&lt;p&gt;Now look at what stayed the same in the picture. I still write prompts and read answers. The agent still works on software from the outside. Those two pipes are as wide as they were in 2023, and far more is trying to get through them.&lt;/p&gt;&#10;&lt;figure class="figure wide"&gt;&lt;img src="https://sergiudm.github.io/p/software-in-the-next-3-years/pipeline-now.svg" alt="2026: the agent, wrapped in a harness, plans, calls tools and works for hours. The two links are marked as bottlenecks. Between you and the agent, what you see has to become words and you read long answers. Between the agent and the software, screenshots, clicks and file reads burn tokens before the work starts." loading="lazy" decoding="async" class="only-light" data-zoom="/p/software-in-the-next-3-years/pipeline-now.svg"&gt;&lt;img src="https://sergiudm.github.io/p/software-in-the-next-3-years/pipeline-now-dark.svg" alt="2026: the agent, wrapped in a harness, plans, calls tools and works for hours. The two links are marked as bottlenecks. Between you and the agent, what you see has to become words and you read long answers. Between the agent and the software, screenshots, clicks and file reads burn tokens before the work starts." loading="lazy" decoding="async" class="only-dark" data-zoom="/p/software-in-the-next-3-years/pipeline-now-dark.svg"&gt;&lt;figcaption&gt;&lt;strong&gt;2026.&lt;/strong&gt; The agent grew and the pipes did not. More waits at each end than fits through, so the red has moved from the middle to the two links.&lt;/figcaption&gt;&lt;/figure&gt;&#10;&#10;&lt;p&gt;So is a chat session good enough for talking to an agent? No, and the reason is different on each side.&lt;/p&gt;&#10;&lt;h3 id="for-the-person-it-is-not-easy"&gt;For the person, it is not easy&lt;a class="anchor" href="#for-the-person-it-is-not-easy" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Much of what I want to tell an agent is something I am looking at. The third bar of a chart. The run whose loss curve jumps near the end. The task that should come before lunch, not after. In a chat box I have to turn what I see into a sentence, and then the agent has to turn the sentence back into the thing and hope it picked the right one. &amp;ldquo;No, the other one&amp;rdquo; is a message I have sent more than once.&lt;/p&gt;&#10;&lt;p&gt;Try it both ways:&lt;/p&gt;&#10;&lt;figure class="demo "&gt;&lt;div class="demo__bar"&gt;&lt;span class="demo__badge"&gt;&lt;svg class="icon icon--spark" viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"&gt;&lt;path d="M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2.5 2.5M15.5 15.5L18 18M6 18l2.5-2.5M15.5 8.5L18 6"/&gt;&lt;/svg&gt;Interactive&lt;/span&gt;&lt;span class="demo__title"&gt;Say it, or point at it&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="demo__body"&gt;&#10;&lt;style&gt;&#10; .pt { display: grid; gap: 14px; }&#10; .pt__task { margin: 0; font-weight: 600; }&#10; .pt__runs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }&#10; .pt__run { display: grid; gap: 4px; padding: 10px 12px; border: 1.5px solid var(--border); border-radius: 10px; background: var(--bg); color: var(--text); font: inherit; text-align: left; cursor: pointer; }&#10; .pt__run:hover { border-color: var(--accent); }&#10; .pt__run[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); }&#10; .pt__run svg { display: block; width: 100%; height: 44px; }&#10; .pt__run path { fill: none; stroke: var(--series-1); stroke-width: 2; vector-effect: non-scaling-stroke; }&#10; .pt__name { font: 600 0.78rem/1.2 var(--font-mono); color: var(--muted); }&#10; .pt__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }&#10; .pt__col { display: grid; gap: 8px; align-content: start; padding: 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); }&#10; .pt__col strong { font: 700 0.78rem/1.2 var(--font-sans); letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }&#10; .pt__col textarea, .pt__msg { box-sizing: border-box; width: 100%; min-height: 84px; margin: 0; padding: 8px 10px; border-radius: 8px; font-size: 0.88rem; line-height: 1.5; }&#10; .pt__col textarea { resize: vertical; border: 1px solid var(--border-strong); background: var(--surface); color: var(--text); font-family: inherit; }&#10; .pt__msg { border: 1px solid var(--border); background: var(--code-bg); color: var(--text); font-family: var(--font-mono); font-size: 0.82rem; white-space: pre-wrap; overflow: hidden; }&#10; .pt__cost { font-size: 0.85rem; color: var(--muted); }&#10; .pt__cost b { color: var(--text); font-variant-numeric: tabular-nums; }&#10; @media (max-width: 640px) { .pt__cols { grid-template-columns: 1fr; } .pt__runs { grid-template-columns: repeat(2, minmax(0, 1fr)); } }&#10;&lt;/style&gt;&#10;&lt;div class="pt" data-pt&gt;&#10; &lt;p class="pt__task"&gt;Two of these six training runs diverged. Tell your agent to rerun them.&lt;/p&gt;&#10; &lt;div class="pt__runs" data-pt-runs&gt;&lt;/div&gt;&#10; &lt;div class="pt__cols"&gt;&#10; &lt;div class="pt__col"&gt;&#10; &lt;strong&gt;Say it&lt;/strong&gt;&#10; &lt;textarea data-pt-text placeholder="Type what you would write in a chat box…" aria-label="Your instruction, in words"&gt;&lt;/textarea&gt;&#10; &lt;span class="pt__cost"&gt;&lt;b data-pt-keys&gt;0&lt;/b&gt; characters, and the agent still has to work out which runs you mean.&lt;/span&gt;&#10; &lt;/div&gt;&#10; &lt;div class="pt__col"&gt;&#10; &lt;strong&gt;Point at it&lt;/strong&gt;&#10; &lt;pre class="pt__msg" data-pt-msg aria-live="polite"&gt;&lt;/pre&gt;&#10; &lt;span class="pt__cost"&gt;&lt;b data-pt-clicks&gt;0&lt;/b&gt; clicks, and the agent gets the runs themselves.&lt;/span&gt;&#10; &lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;script&gt;&#10;(function () {&#10; var root = document.querySelector('[data-pt]');&#10; if (!root) return;&#10; var runs = root.querySelector('[data-pt-runs]');&#10; var msg = root.querySelector('[data-pt-msg]');&#10; var text = root.querySelector('[data-pt-text]');&#10; var diverged = { 1: true, 5: true };&#10; var clicks = 0;&#10; function curve(k) {&#10; var d = '';&#10; for (var i = 0; i &lt;= 24; i++) {&#10; var v = 0.86 * Math.exp(-i / (5 + k % 3)) + 0.08 + 0.02 * Math.sin(i * 1.7 + k * 2.1);&#10; if (diverged[k] &amp;&amp; i &gt; 12) v += 0.78 * Math.pow((i - 12) / 12, 1.5) + 0.03 * Math.sin(i * 2.6);&#10; d += (i ? ' L' : 'M') + (i / 24 * 100).toFixed(1) + ',' + (42 - Math.min(v, 1) * 38).toFixed(1);&#10; }&#10; return d;&#10; }&#10; function update() {&#10; var picked = [];&#10; runs.querySelectorAll('[aria-pressed="true"]').forEach(function (b) { picked.push(' "' + b.dataset.run + '"'); });&#10; msg.textContent = picked.length ? 'rerun([\n' + picked.join(',\n') + '\n])' : '// click the runs above';&#10; root.querySelector('[data-pt-clicks]').textContent = clicks;&#10; root.querySelector('[data-pt-keys]').textContent = text.value.length;&#10; }&#10; for (var k = 0; k &lt; 6; k++) {&#10; var b = document.createElement('button');&#10; b.type = 'button';&#10; b.className = 'pt__run';&#10; b.dataset.run = 'run-0' + (k + 1);&#10; b.setAttribute('aria-pressed', 'false');&#10; b.innerHTML = '&lt;span class="pt__name"&gt;run-0' + (k + 1) + '&lt;/span&gt;&lt;svg viewBox="0 0 100 44" preserveAspectRatio="none" aria-hidden="true"&gt;&lt;path d="' + curve(k) + '"/&gt;&lt;/svg&gt;';&#10; b.addEventListener('click', function () {&#10; this.setAttribute('aria-pressed', this.getAttribute('aria-pressed') === 'true' ? 'false' : 'true');&#10; clicks += 1;&#10; update();&#10; });&#10; runs.appendChild(b);&#10; }&#10; text.addEventListener('input', update);&#10; update();&#10;})();&#10;&lt;/script&gt;&#10;&lt;/div&gt;&lt;figcaption&gt;The six runs are the same in both cases. Only the way you answer changes.&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;The other direction is no better. The agent works for twenty minutes and hands me eight hundred words about it. I needed three things: did it work, what changed, and is there anything I have to decide. They are in there somewhere, and reading is the slowest thing I do all day.&lt;/p&gt;&#10;&lt;h3 id="for-the-agent-it-is-not-efficient"&gt;For the agent, it is not efficient&lt;a class="anchor" href="#for-the-agent-it-is-not-efficient" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Before an agent can do what I asked, it has to find out what state the app is in. If the app only has a GUI, that means a screenshot, a model call to read it, a click, and another screenshot. If the app is a folder of code and data, it means reading files until it understands where things live. All of those tokens go into learning something the app already knows. The app just has no way to say it.&lt;/p&gt;&#10;&lt;p&gt;Here is one real number. Everything my &lt;a href="https://github.com/AutoPhd-org/schedule-everything" rel="noopener"&gt;Schedule Everything&lt;/a&gt; workspace can show across its five pages (today&amp;rsquo;s timeline, every task, every deadline, habits, history) comes out of one command as a JSON document of about 17 KB, roughly 5,000 tokens.&lt;span class="sidenote-wrap"&gt;&lt;label for="sn-4" class="sidenote-ref" aria-label="Show note"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-4" class="sidenote-toggle"&gt;&lt;span class="sidenote" role="note"&gt;Measured on my own data with &lt;code&gt;rmd state | wc -c&lt;/code&gt;. The token count is an estimate at 3 to 4 bytes per token.&lt;/span&gt;&lt;/span&gt; That is about the price of a few screenshots, each of which shows one screen of one page and still has to be read by a vision model that may misread it.&lt;/p&gt;&#10;&lt;p&gt;So I keep coming back to two questions.&lt;/p&gt;&#10;&lt;blockquote class="pull"&gt;&#10; &lt;p&gt;Who said people must convey information in words? And who said an agent must walk an app from its front to its back to read and change its state?&lt;/p&gt;&#10;&#10;&lt;/blockquote&gt;&lt;h2 id="interface-engineering"&gt;Interface engineering&lt;a class="anchor" href="#interface-engineering" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Each stage of this story got its own kind of engineering. When the model was all there was, we worked on what we said to it: prompt engineering. When the limit became what the model had in front of it, we worked on that: context engineering. When the limit became the loop around the model, we built harnesses. I think the limit is now the place where the person, the agent and the software meet, and I call working on it &lt;strong&gt;interface engineering&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;p&gt;It asks two things of any piece of software:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;How does the agent learn the app&amp;rsquo;s state and change it?&lt;/strong&gt; The best answer is directly and in one step, without going through pixels.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;How does the person tell the agent what they mean, and find out what it did?&lt;/strong&gt; The best answer is whichever way is quickest: a word, a click, a stroke of a pen, a key.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="work-that-already-points-this-way"&gt;Work that already points this way&lt;a class="anchor" href="#work-that-already-points-this-way" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I am far from the first to notice. Several projects already repair one of the arrows in the picture.&lt;/p&gt;&#10;&lt;div class="table-wrap"&gt;&#10;&lt;table&gt;&#10; &lt;thead&gt;&#10; &lt;tr&gt;&#10; &lt;th&gt;Work&lt;/th&gt;&#10; &lt;th&gt;Which arrow it repairs&lt;/th&gt;&#10; &lt;th&gt;What is left&lt;/th&gt;&#10; &lt;/tr&gt;&#10; &lt;/thead&gt;&#10; &lt;tbody&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Computer-use agents&lt;/td&gt;&#10; &lt;td&gt;None, on purpose. The agent uses the GUI as it is, so no app has to change.&lt;/td&gt;&#10; &lt;td&gt;It is the most expensive route there is: every step costs a screenshot and a model call.&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;&lt;a href="https://github.com/HKUDS/CLI-Anything" rel="noopener"&gt;CLI-Anything&lt;/a&gt;&lt;/td&gt;&#10; &lt;td&gt;Agent to software. It reads an app&amp;rsquo;s source code and generates a command-line harness with JSON output, so the agent runs commands and does not click.&lt;/td&gt;&#10; &lt;td&gt;It serves the agent only. Nothing changes for the person: there is no shared view, and no say over what the agent may see.&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener"&gt;MCP&lt;/a&gt; and &lt;a href="https://github.com/webmachinelearning/webmcp" rel="noopener"&gt;WebMCP&lt;/a&gt;&lt;/td&gt;&#10; &lt;td&gt;Agent to software. An app, or a web page, declares the tools an agent may call.&lt;/td&gt;&#10; &lt;td&gt;The person is not in the picture. What the agent did comes back as text in a chat.&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps/" rel="noopener"&gt;MCP Apps&lt;/a&gt;&lt;/td&gt;&#10; &lt;td&gt;Agent to person. A tool can return an interactive widget that renders inside the conversation.&lt;/td&gt;&#10; &lt;td&gt;The widget lives in the chat. The app&amp;rsquo;s own page, where the work really is, is somewhere else.&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;&lt;a href="https://openai.com/index/gpt-6-for-everyone/" rel="noopener"&gt;Intelligent UI&lt;/a&gt; in ChatGPT, with GPT-6&lt;/td&gt;&#10; &lt;td&gt;Agent to person, and a little of person to agent. The model composes its answer from charts, forms and tappable buttons as well as paragraphs.&lt;/td&gt;&#10; &lt;td&gt;The interface is made for one answer and stays in the chat. It is not the state of the apps you already use.&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;OpenAI ends its announcement of Intelligent UI with a sentence I agree with: &amp;ldquo;Instead of people adapting to software, software will adapt to people.&amp;rdquo; I would only aim it wider than the chat window, at the software itself.&lt;/p&gt;&#10;&lt;p&gt;Taken together, these tell me the direction is right, and they also show the gap. Each one repairs a single arrow. None of them treats the app as the place where the person and the agent meet.&lt;/p&gt;&#10;&lt;h2 id="what-i-think-software-should-look-like"&gt;What I think software should look like&lt;a class="anchor" href="#what-i-think-software-should-look-like" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Two changes, and both are changes to the app, not to the model.&lt;/p&gt;&#10;&lt;h3 id="1-the-agent-works-on-the-apps-state-with-your-authorization"&gt;1. The agent works on the app&amp;rsquo;s state, with your authorization&lt;a class="anchor" href="#1-the-agent-works-on-the-apps-state-with-your-authorization" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;figure class="figure wide"&gt;&lt;img src="https://sergiudm.github.io/p/software-in-the-next-3-years/direct-state.svg" alt="An app has one state document and a page that is one view of it. You use the page. Your agent reads the state and runs commands directly, through access switches you set for each part of the state: whether it can see it, and whether it may act, must ask first, or may not act. The route agents take today, through screenshots and clicks on the page, is drawn as a long detour." loading="lazy" decoding="async" class="only-light" data-zoom="/p/software-in-the-next-3-years/direct-state.svg"&gt;&lt;img src="https://sergiudm.github.io/p/software-in-the-next-3-years/direct-state-dark.svg" alt="An app has one state document and a page that is one view of it. You use the page. Your agent reads the state and runs commands directly, through access switches you set for each part of the state: whether it can see it, and whether it may act, must ask first, or may not act. The route agents take today, through screenshots and clicks on the page, is drawn as a long detour." loading="lazy" decoding="async" class="only-dark" data-zoom="/p/software-in-the-next-3-years/direct-state-dark.svg"&gt;&lt;figcaption&gt;The page is one view of the state. The agent reads the same state through switches you set, and no longer has to go through the page.&lt;/figcaption&gt;&lt;/figure&gt;&#10;&#10;&lt;p&gt;An app keeps its whole state in one document and offers one command per action. The page I look at is a view of that document and has no private channel, so nothing exists only on screen. The agent reads the same document in one call, and when it acts, the page in front of me changes at once.&lt;/p&gt;&#10;&lt;p&gt;I stay in charge of how far it reaches. For each part of the state I decide whether the agent can see it, and whether it may act, has to ask first, or may not touch it. It is the same kind of decision as camera and location permissions on a phone, and it should be just as easy to find.&lt;/p&gt;&#10;&lt;p&gt;The first half already exists. It is the contract behind &lt;a href="https://github.com/AutoPhd-org/launchx" rel="noopener"&gt;LaunchX&lt;/a&gt;, the launcher I use for my local apps: an app serves its state at one address and ships a CLI that can do everything its page can, and LaunchX has a switch per app for whether my agents are told about it.&lt;/p&gt;&#10;&lt;figure class="terminal "&gt;&#10; &lt;div class="terminal__bar"&gt;&#10; &lt;span class="terminal__dots" aria-hidden="true"&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;/span&gt;&#10; &lt;span class="terminal__title"&gt;~ · zsh&lt;/span&gt;&#10; &lt;button class="copy-btn" type="button" data-copy-commands aria-label="Copy commands"&gt;&lt;svg class="icon icon--copy" viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"&gt;&lt;rect x="9" y="9" width="11" height="11" rx="2"/&gt;&lt;path d="M5 15V6a2 2 0 0 1 2-2h8"/&gt;&lt;/svg&gt;&lt;span&gt;Copy&lt;/span&gt;&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;pre class="terminal__body"&gt;&lt;code&gt;&lt;span class="term-line term-cmd"&gt;&lt;span class="term-prompt" aria-hidden="true"&gt;$ &lt;/span&gt;&lt;span class="term-input"&gt;lx agents&lt;/span&gt;&lt;/span&gt;&#10;&lt;span class="term-line term-out"&gt;Apps that agents can discover:&lt;/span&gt;&#10;&lt;span class="term-line term-out"&gt;&lt;/span&gt;&#10;&lt;span class="term-line term-out"&gt;ID NAME STATUS CLI SKILLS&lt;/span&gt;&#10;&lt;span class="term-line term-out"&gt;rmd Schedule Everything running rmd build-schedule, plan-day, suggest-tasks&lt;/span&gt;&#10;&lt;span class="term-line term-out"&gt;vmc Machine Console running vmc —&lt;/span&gt;&#10;&lt;span class="term-line term-out"&gt;&lt;/span&gt;&#10;&lt;span class="term-line term-out"&gt;Agents find them through the `launchx` skill in ~/.claude/skills and ~/.agents/skills.&lt;/span&gt;&#10;&lt;span class="term-line term-cmd"&gt;&lt;span class="term-prompt" aria-hidden="true"&gt;$ &lt;/span&gt;&lt;span class="term-input"&gt;rmd state | wc -c&lt;/span&gt;&lt;/span&gt;&#10;&lt;span class="term-line term-out"&gt;17177&lt;/span&gt;&#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;aside class="callout callout--note"&gt;&#10; &lt;p class="callout__title"&gt;&lt;svg class="icon icon--note" viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"&gt;&lt;circle cx="12" cy="12" r="9"/&gt;&lt;path d="M12 11v5M12 8h.01"/&gt;&lt;/svg&gt;&lt;span&gt;What the switch does today&lt;/span&gt;&lt;/p&gt;&#10; &lt;div class="callout__body"&gt;&lt;p&gt;The LaunchX switch is per app, and it decides what agents are &lt;em&gt;told about&lt;/em&gt;, not what they can reach. Switches for each part of an app, with real enforcement behind them, are the next thing to build. The figure shows where this is heading.&lt;/p&gt;&lt;/div&gt;&#10;&lt;/aside&gt;&lt;h3 id="2-you-and-the-agent-share-one-interface"&gt;2. You and the agent share one interface&lt;a class="anchor" href="#2-you-and-the-agent-share-one-interface" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;figure class="figure wide"&gt;&lt;img src="https://sergiudm.github.io/p/software-in-the-next-3-years/shared-interface.svg" alt="One app window that you and your agent both use. On a task board, a task the agent suggested has Add and Reject buttons, a task you circled with a pen carries your handwritten note, and a task the agent just added can be undone. A bar lists shortcut keys. A side panel has switches for what the agent can see and do in each part of the app, and the chat box is still there." loading="lazy" decoding="async" class="only-light" data-zoom="/p/software-in-the-next-3-years/shared-interface.svg"&gt;&lt;img src="https://sergiudm.github.io/p/software-in-the-next-3-years/shared-interface-dark.svg" alt="One app window that you and your agent both use. On a task board, a task the agent suggested has Add and Reject buttons, a task you circled with a pen carries your handwritten note, and a task the agent just added can be undone. A bar lists shortcut keys. A side panel has switches for what the agent can see and do in each part of the app, and the chat box is still there." loading="lazy" decoding="async" class="only-dark" data-zoom="/p/software-in-the-next-3-years/shared-interface-dark.svg"&gt;&lt;figcaption&gt;One page for both of you. Five ways to answer, and the chat box is only one of them.&lt;/figcaption&gt;&lt;/figure&gt;&#10;&#10;&lt;p&gt;The second change is where the conversation happens. The agent no longer describes the app to me in a chat window. We both work on the app&amp;rsquo;s own page: what it does shows up there, and what I do, it can see.&lt;/p&gt;&#10;&lt;p&gt;Once we share a surface, I can answer in whichever way is quickest:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Words.&lt;/strong&gt; The chat box stays. &amp;ldquo;Plan my afternoon&amp;rdquo; is best said as a sentence.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Yes / no buttons.&lt;/strong&gt; Most of my replies to an agent are yes or no, and a button beats typing &amp;ldquo;yes, go ahead&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;A pen.&lt;/strong&gt; Circle the thing and write &amp;ldquo;split this in two&amp;rdquo; next to it. The stroke lands on an object in the app&amp;rsquo;s state, so the agent knows which task I mean without my describing it.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Shortcut keys.&lt;/strong&gt; &lt;kbd&gt;A&lt;/kbd&gt; to add, &lt;kbd&gt;R&lt;/kbd&gt; to reject, &lt;kbd&gt;Z&lt;/kbd&gt; to undo what the agent just did.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Switches.&lt;/strong&gt; The access panel from the first picture, on the page, where I can find it.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Part of this runs today too. In Schedule Everything, when my agent goes through my inbox and calendar for things I should do, it never adds a task itself. Its suggestions appear on my task board, dashed and labelled with who suggested them and why, each with two buttons: &lt;strong&gt;Add to my tasks&lt;/strong&gt; and &lt;strong&gt;Reject&lt;/strong&gt;. That is a yes/no button on a shared page, and it replaced a paragraph of chat. I wrote about the rest of that app in &lt;a href="https://sergiudm.github.io/p/time-management/"&gt;the previous post&lt;/a&gt;. The pen and the shortcut keys are not built yet.&lt;/p&gt;&#10;&lt;h2 id="what-this-is-not"&gt;What this is not&lt;a class="anchor" href="#what-this-is-not" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;At &lt;a href="https://github.com/AutoPhd-org" rel="noopener"&gt;AutoPhD&lt;/a&gt; we do not make better models, and we do not make better harnesses. Both are improving quickly without us, and an app built this way should work with whichever harness you use next year.&lt;/p&gt;&#10;&lt;p&gt;All we try to do is build the best interface for each scene of daily life and work where a person and an agent meet: one where they understand each other easily, and the agent gets the job done for less. We start with the scenes I live in as a PhD student: planning a week, reading papers, keeping an eye on GPU servers.&lt;/p&gt;&#10;&lt;h2 id="three-years"&gt;Three years&lt;a class="anchor" href="#three-years" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;So what should software look like three years from now? These are my bets.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Apps ship their state.&lt;/strong&gt; A state document and a list of commands become as ordinary as a mobile layout is today. An app without them will feel like a website that does not work on a phone.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Agent access is a settings page.&lt;/strong&gt; What an agent can see and do in an app is a panel of switches you can find in ten seconds.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The chat box is one control among several.&lt;/strong&gt; It sits beside buttons, pens and keys, on the app&amp;rsquo;s own page.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The GUI does not go away.&lt;/strong&gt; It becomes the place where you and your agent meet.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;If I am right, what decides whether software does well in the next three years is how easily a person and an agent can use it together, more than how smart the model inside it is.&lt;/p&gt;&#10;&lt;p&gt;The code so far is at &lt;a href="https://github.com/AutoPhd-org" rel="noopener"&gt;AutoPhD&lt;/a&gt;: &lt;a href="https://github.com/AutoPhd-org/launchx" rel="noopener"&gt;LaunchX&lt;/a&gt; with its &lt;a href="https://github.com/AutoPhd-org/launchx/blob/main/SPEC.md" rel="noopener"&gt;contract&lt;/a&gt;, and &lt;a href="https://github.com/AutoPhd-org/schedule-everything" rel="noopener"&gt;Schedule Everything&lt;/a&gt;. If you build an app this way, or think I have this wrong, open an issue there. I would like to hear it.&lt;/p&gt;&#10;</description></item></channel></rss>