All writing

Strivelabs engineering · Agent UI

Who writes the UI? How agent interfaces evolved in Strive

How we went from writing a UI for every agent to letting the agent write its own. A scaffold and three hooks for agent builders, rails that coding agents could follow, the turn to one chat, and what we learned at each step.

Published
Stack
React · TypeScript · React Router · Zustand
Read
~12 minutes

Every agent on Strive needs a way to reach the people who use it: a page to set it up, a way to start it, and somewhere to review what it did.

This post is the journey of how we built those interfaces, from June 2025 to today, and how the answer to “who writes the UI?” kept changing.

Each step handed a little more of the UI to the agent.

The one-liner

We went from developers writing every screen, to coding agents writing them on our rails, to a chat that answers in generated UI, to agents writing their own.

Four answers to “who writes the UI?”, in the order we gave them.

A little bit of backstory

It started in June 2025. As a team, we were building agent prototypes for different use cases, from our early clients and friends.

We already had a solid backend for orchestrating workflows quickly. Almost anything you can do in n8n, you could do on our platform. So the idea was simple: build a UI for each workflow fast, demo it to customers, validate the idea, and find product-market fit.

It began with a tip from our CTO: come up with one standard way of putting a UI on an agent. The backend was already plug and play. Agent builders could drop components into a DAG and have a workflow ready in no time. The challenge was doing the same for the UI.

If the backend is plug and play, the UI should be too.

Most agents needed the same few pages: one to configure the agent, one to install it, and one to review what it did and check its status. Configuration-driven UI was the long-term goal, but it wasn’t ready for the first agents, and we weren’t going to wait for it.

So our CTO proposed screens: a standard set of pages every agent plugs into. I formulated the framework around it, in React. I wrote the doc, built the abstractions over the router and the store, and made the scaffold, the hooks and everything around them. The rest of the team used the framework to build their agents.

A builder for us, not for everyone

At first we pictured this becoming our external developer experience too. A lightweight agent-builder framework, where anyone could build agents on Strive the same way we did. To get there, we’d build more agents ourselves, improve the DX over time, and open it up once it was stable.

But we didn’t want to enter the agent-builder space. It was already crowded, with n8n, Lindy, Make and Relevance AI, and we wouldn’t have had a real reason to stand out.

Compete on the agents, not on the builder.

It wasn’t that we lacked the pieces. We had everything. We knew how to build one; we’d done it before, and we could power it later if the demand came. For now, we wanted to stand out in a crowded agent space with agents that were effective and reliable.

So the focus became our own developer experience: helping the team build agent UIs fast. The line at the end of the handover guide sums it up:

The platform handles the complexity. You focus on the business logic that’s specific to your agent.

One command to a working agent

The simplest way to make agent interfaces pluggable was a good scaffold. If it came with every capability wired up and a working example, builders could focus on the agent and fill in the interface.

So pnpm clone-agent-template <agent-name> writes a new folder under src/agents/ with a minimal agent that already triggers a workflow, shows progress and renders output.

One command, a working agent. The builder edits three files and keeps the rest.
What each generated file gives the builder
FileWhat’s in it
constants/index.tsA target node ID placeholder, and a two-node list the progress view reads
TriggerScreen.tsxA react-hook-form and zod form with two sample fields and validation, whose submit starts the workflow
LoadingScreen.tsxA card around StepLoadingProgress, fed by the node list
OutputScreen.tsxLoading and error states, “Try again”, a copy button and a JSON preview of the result, ready to be replaced

After it runs, the script prints the next steps: set the node IDs, change the form fields, shape the output view. Nobody starts from a blank folder.

Builders only needed to know how to call the backend and wire its data to the screens. No new components, no thinking about padding or colours, and no need to learn React Router or how state works in Zustand.

The platform handles the complexity

An agent’s run has three moments: start a workflow, read its output, start over. I gave each one a hook, and made the hooks the only way in.

Builders write above the line. The platform owns everything below it.

Here’s an agent’s whole run, from the builder’s side.

Start, read, start over
// TriggerScreen: start a runconst { trigger, isLoading } = useAgentWorkflowTrigger();trigger({  data: [{ data: formValues, scenario: "default" }],  callbacks: { onSuccess: (executionId) => { /* agent-specific */ } },});// OutputScreen: read only the node you care aboutconst { getNodeOutput, hasData, error } = useAgentOutput({  targetNodeIds: [nodeIds.TARGET_NODE],  transformOutput: (raw) => toRows(raw),});// “New session”: back to the formconst { resetAgent } = useAgentReset();

Agent-specific behaviour goes in the callbacks. Anything else an agent needed later, like configuration, approvals or reading task queues, got a hook of its own, so builders kept calling hooks instead of reaching into the store.

Make the right way the short way.

Consistent by default

Consistency was the part I cared about most as a design engineer. Agents built by different people could easily end up as different products.

So the template uses only our component registry, @strivelabs/ui-core, and lucide-react icons, and every agent moves through the same three screens.

The easiest path is the consistent one.

Here’s one of the first agents built that way, the Google Ads campaign analyser. Like most of them, it was a rapid prototype I designed straight in code.

One of the first agents on screens, the Google Ads campaign analyser: an empty state to start, a full audit when it’s done.
The easiest path should be the consistent one.

Relax the rails

Within a month, the screens felt too restrictive. They kept every agent inside one narrow model of a UI, just as we started building agents that went in completely different directions.

A couple of content agents needed rich, multi-view flows. A sales agent needed far less, and none of those components. Squeezing both into the same set of screens helped neither of them.

The screen tree was part of the problem. It picked a screen from a state in the store and loaded it by folder name. That kept things simple, but you couldn’t share a view, refresh it or come back to it.

If a view matters, it deserves a URL.

So in July 2025, I relaxed it. Path-based routing took the one-model limit away: each new agent registers its own routes, declares its views and a layout, and adds one line to App.tsx. Agents that work through a queue of tasks, like the content agents, share one layout, so a new one is only a short wrapper.

It worked because of who was building by then. The team was building with coding agents, which could put together an interface that fits from our primitives, as long as the rails were clear. More on that in the next section.

Fit the interface to the use case, not the use case to the interface.
One route, or one wrapper
// App.tsx: above the generic route, or the generic layout catches it first<Route path="/ui/agent/my-agent/*" element={<MyAgentLayout />} /><Route path="/ui/agent/:agentId" element={<AgentLayout />} />// a queue-style agent: a short wrapper around the shared layout<BlogContentAgentLayout  agentShortName="my-agent"  routes={ROUTES}  bucketConfig={buckets}  screens={{ Trigger, Output, Result, Review }}/>

Here’s one of the content agents on that shared layout. Four views, one short wrapper, and a URL for each.

The content refresher: trigger, output, result and review, on one shared layout.

Both patterns still live side by side, on the same hooks and the same backend. Only rendering and navigation differ.

When coding agents became builders too

By this time, agentic coding had become far more accessible, and far better. Our team was using coding agents heavily to write frontend code.

They were comfortable building rough prototypes with just the primitives: the design system and standard React guidelines. A coding agent could follow the design system, read a similar agent in the codebase and build a rich UI on its own.

So at the same time, I added skills for coding agents: one more layer of abstraction. Builders didn’t need to keep the platform’s UI concepts in their heads, or in their coding agent’s context. The skills carry what the handover guide tells a developer: the hooks, the components, the rules and the pitfalls.

Good rails work for any builder. The guide, the design system and one good example were enough to keep a coding agent on them.

Most of the rules were already there for people. They just mattered more once something was following them literally: use the platform hooks, don’t write to the store, build only from ui-core, follow the existing agents’ patterns.

Where it started to hurt

Thirteen agents were built on this foundation. Five have their own routes, and seven still use the screen tree. Writing it all down for this post turned up a few honest gaps.

The scaffold fell behind

We moved new agents to routes, but the scaffold still writes the old screen tree. So a brand new agent starts on the pattern we’re moving away from.

The guide lived in three places

The handover guide moved, and the copies drifted. Hook names changed in one place and not the others. People can spot a stale doc. A coding agent follows it faithfully.

Quiet contracts

The trigger expects an array of { data, scenario } items. The starter form passed a plain object, and the hook quietly replaced it with an empty array. Every agent copied from the template inherited that.

Thirteen UIs to keep in step

Every fix to the shared parts was easy. Everything else, every new screen and every change of pattern, had to happen thirteen times.

Read together, those aren’t four bugs. They’re one: every agent still had its own UI, and every one of them was ours to keep in step.

Agents can’t work in silos

From there, we realised something bigger. These agents couldn’t keep working in silos.

The agentic world was moving in a different direction too. Tasks weren’t siloed any more, and agents needed to work together. Chat was emerging as the natural way to reach everything agents can do.

Looking back, the hint was there early. The campaign analyser already ended with “Want to dive deeper? Chat now”.

We sensed it, and started moving to generative UI right away: Strive AI, one chat that answers in generated UI.

Agents stopped working alone, so their UIs couldn’t either.

Nothing was thrown away. We knew some complex, dedicated tasks still need a much wider experience than a chat reply, like refreshing a piece of content or writing a draft. The draft view you saw earlier is one of them.

That’s how we landed on agent slots: agent views that open beside the thread, with one agent UI shared between chat and the standalone app. The design system became the base of the chat’s blocks, and the routes we’d added meant chat could deep-link straight into an agent.

Here’s the content refresher, before and after.

The content refresher on its own page: a count of blog posts ready for refresh, an add new blog post button, and a table of blog post URLs with their keywords, status, date and a view or review action. The same content refresher inside Strive AI: the chat on the left starts the blog refresh agent, and the same table of blog posts, keywords, status and review actions opens in a panel beside the thread.
Drag to compare. Same agent, same UI. Now it opens beside the conversation that asked for it.
Nothing was thrown away. It moved into the chat.

How we built that engine is a story of its own, and it has its own post.

Let the agent write its own UI

Chat composes UI from our own components, which is right where consistency matters. Reports needed something looser: big volumes of data, layouts nobody could predict, and a story you get in one scan.

So for reports we went one step further, and let the agent write the interface itself, as HTML in a sandboxed canvas.

Controlled output and agent-written UI, side by side. The registry keeps the chat consistent; the agent gets the surfaces a schema would only get in the way of.

That one is the story of Strive Studio. For this post, what matters is the shape of the whole journey. My job moved from writing screens, to writing the rails others build on, to writing the taste an agent builds with.

And reports are only the start. Letting the agent write its own code is where we’re heading.

What I’d keep

  1. Compete where you can stand out. We put our effort into reliable agents, not into one more agent builder.
  2. Make the right way the short way. Builders called hooks instead of touching the store, because hooks were less work.
  3. The easiest path should be the consistent one. Only ui-core in the template meant consistency came for free.
  4. Relax the rails before they break. A content agent and a sales agent don’t need the same shape.
  5. Write the guide for every reader. People and coding agents read the same page, and agents follow it literally.
  6. Move the template the day the pattern moves. The scaffold is the doc people actually copy.
  7. Abstractions buy speed, not scale. Once agents had to work together, the answer was one surface, not a faster way to build many.

On one timeline

One UI per agent · built by people

  1. Jun 2025

    The scaffold

    One standard way to put a UI on an agent: pnpm clone-agent-template, three platform hooks, ui-core only, and a handover guide

  2. Jul 2025

    Relaxed to routes

    Content and sales agents pull in different directions; path-based routing and a shared layout for queue-style agents

  3. Jul 2025

    Skills for coding agents

    One more layer of abstraction, so builders and their coding agents can leave the platform’s UI concepts to the skill

One chat · generated UI

  1. Sep 2025

    Strive AI

    A chat that answers in generated UI, built from our own components

  2. Feb to Mar 2026

    Slots and skills

    Report preview and content editor slots open beside the thread

The agent writes its own UI

  1. 26 Apr 2026

    HTML in a sandbox

    First agent-written HTML report, hosted in a sandboxed iframe

  2. 11 Jun 2026

    Folded into chat

    Strive Studio becomes a skill, with the report in a canvas beside the thread

From one UI per agent, built by people and then coding agents, to one chat with generated UI, to agents writing their own.

What’s next

We started by making agent UIs cheap to build. We ended up letting the agent build them. We’re already in that final stage, and excited for what’s ahead.

Thanks

Agent UI on Strive is a team effort, from the workflows and agents underneath to the people who built on the scaffold and told us where it hurt. Thanks to our CTO for the idea of screens that started it all, and to the whole team at Strivelabs for building it together.

Back to the shelf