The interface materialises the system
A lot of people currently imagine that agents will make dashboards and traditional interfaces unnecessary. If an agent can perform every action for us, why keep the screens?

Because an interface does more than accept input.
It materialises the system.
A dashboard gives humans somewhere concrete to see:
- What exists
- What has changed
- What the system can do
- Which permissions are active
- How operations relate to one another
- What the agent actually did
- Whether the result matches their mental model
When everything is hidden behind an agent or an abstraction, the product becomes harder to understand and remember.
I have experienced this in my own work. I built systems with so many layers of abstraction that, later, I could no longer remember how they worked internally.
The code was technically organised. The abstractions looked sensible in isolation. But the actual behaviour had disappeared from my mental model.
It is similar to creating a deep hierarchy of abstract classes. Eventually, you must traverse several layers just to understand what one action really does.
Agents can create the same problem at the product level.
A user asks for an outcome. The agent discovers several tools, makes decisions, executes multiple operations and returns a polished answer.
The task was completed, but the user learned almost nothing about the system.
They may not know what changed, which permissions were used, which operation failed or how to reproduce the result without the agent.
A good interface prevents this.
It gives the product a stable, spatial form. Operations have names. State has a location. Permissions are visible. Relationships can be inspected. Agent actions leave traces.
The abstract machinery becomes something a person can see and remember.

The goal is not to replace the interface with an agent. It is to connect both to the same capability spine:
The agent provides speed, intent resolution and orchestration.
The interface provides visibility, orientation, control and memory.
Both are essential.
"If the spine makes the product executable everywhere, the interface makes the product understandable to humans."
When every surface builds its own product
Most software begins with an interface.
A user clicks a button. A page validates a form, calls an endpoint, writes to a database and updates the screen.
Then the product grows.
Customers want an API. Operators need a CLI. Developers ask for a typed SDK. Mobile applications appear. Eventually, agents need tools that can understand and operate the product.
Each new surface is usually added by connecting it directly to whatever implementation already exists.
Before long, the same product action exists in several different forms:
- The web interface has its own handler.
- The API route repeats the validation.
- The CLI calls an internal script.
- The agent tool talks directly to the database.
- The mobile application behaves slightly differently again.
They may all appear to perform the same action, but they no longer share the same meaning, permissions or behaviour.
The product has become a collection of surfaces without a shared brain.
A product is not its pages
We often model software around what users can see.
We talk about the dashboard, the settings page, the editor, the chat interface or the administration console.
But those are representations of the product. They are not the product itself.
The product is what the system can do:
todo.create
todo.complete
invoice.approve
document.publish
workspace.invite
customer.suspend
deployment.rollbackThese are its capabilities, its vocabulary of meaningful actions.
Each capability has more structure than a function name. The system must know:
- What input it accepts
- What it returns
- Whether it changes state
- Who may execute it
- Which workspace, tenant or resource it may affect
- Which policy governs it
- How its effects become visible
- Which surfaces may expose it
Once these capabilities are explicit, the product is no longer trapped inside one interface.
It can be used by a person, an agent, an application, an API consumer or a command-line tool without reimplementing its behaviour for each one.
The capability spine
The architecture I keep returning to is a shared capability spine:
Every surface has a different interaction model, but each one calls the same product operations.
The UI translates human interaction into SDK calls. The API translates HTTP requests. The CLI translates commands. The agent tools translate intent into selectable operations.
None of these surfaces gets to redefine what an operation means.
The spine owns that meaning.

One operation, every surface
Imagine a simple operation:
todo.createIts contract might look like this:
const createTodo = {
id: "todo.create",
input: {
scopeId: "string",
text: "string",
},
capability: "todo.create",
mutates: true,
http: {
method: "POST",
path: "/scopes/:scopeId/todos",
},
agent: {
name: "todo_create",
description: "Create a Todo inside a scope.",
},
};This is more than API documentation.
It gives the operation a stable identity. It describes its inputs, permission and available surfaces.
The implementation follows one guarded path:
1. Validate input
2. Resolve identity
3. Check capability
4. Check scope
5. Execute the workflow
6. Persist the result
7. Publish the live revision
8. Return the resultThe application can use the typed SDK:
await client.todos.create({
scopeId: "acme",
text: "Prepare launch notes",
});An external integration can call REST:
POST /scopes/acme/todos
Authorization: Bearer $API_KEY
{
"text": "Prepare launch notes"
}An operator can use the CLI:
todo create "Prepare launch notes" --scope acmeAn agent can select the tool:
await executeProductTool("todo.create", {
scopeId: "acme",
text: "Prepare launch notes",
});These are not four implementations.
They are four ways of reaching the same capability.
The minimum viable spine
The full architecture can sound larger than it is. The smallest useful version only needs a clear home for the operation contract, its implementation, its infrastructure boundary and the surfaces that call it.
This is enough structure to make those responsibilities visible:
product/
├── contracts/
│ └── operations.ts
├── workflows/
│ └── create-todo.ts
├── ports/
│ └── todo-store.ts
├── sdk/
│ ├── todos.ts
│ └── product-tools/
│ ├── registry.ts
│ └── handlers/todos.ts
├── adapters/
│ ├── http/todos.ts
│ ├── cli/todos.ts
│ ├── mcp/todos.ts
│ └── system/todo-store.ts
└── app/
└── live/use-todos-live.tsThe tree is deliberately boring. You should be able to look at it once and know where the product vocabulary lives, where mutations are coordinated, where infrastructure enters and where each surface connects.
The contract gives the operation a stable identity:
export const todoCreate = {
id: "todo.create",
input: z.object({
scopeId: z.string(),
text: z.string().min(1),
}),
capability: "todo.create",
mutates: true,
};This mutation uses a workflow to coordinate its steps through a port:
export async function runCreateTodo(
input: TodoCreateInput,
dependencies: { todos: TodoStore },
) {
const todo = todoCreate.input.parse(input);
return dependencies.todos.create(todo);
}The SDK applies context and becomes the shared entry point:
export async function createTodo(
input: TodoCreateInput,
context: ProductContext,
) {
authorize(context, "todo.create", input.scopeId);
return runCreateTodo(input, { todos: todoStore });
}Every surface is then a thin translation into that entry point:
Application: client.todos.create(input)
HTTP: sdk.todos.create(input, context)
CLI: sdk.todos.create(input, context)
Agent: executeProductTool("todo.create", input)The minimum rule is simple:
Every surface calls the SDK. The SDK applies the capability guard, executes the operation, crosses a port and reaches the infrastructure. An operation can be a direct call or a workflow when several steps need coordinating.
AI may write most of these files for us, but that makes the visible structure more important, not less. When generation is cheap, orientation becomes the scarce resource.
I do not need to remember every line. I do need to see where meaning lives, which direction dependencies travel and where a new capability belongs. The file tree becomes an interface for understanding the codebase.
Permissions become understandable
Once every operation has a stable identity, those operation IDs can become permissions.
An administrator may have:
todo.list
todo.create
todo.update
todo.deleteA mobile integration may have:
todo.list
todo.createAn intern agent may have:
todo.listThe permission does not belong to the chat interface or the API route. It belongs to the product operation.
Every caller also carries context:
{
principalId: "intern-agent",
capabilities: ["todo.list"],
scopeIds: ["acme"]
}If that agent attempts todo.create, the request stops at the shared capability guard.
It does not matter whether the request arrived through an application, agent, API or CLI. It encounters the same decision.

This gives us something more precise than "the agent has access to the application."
The agent has access to a selectable collection of scoped product capabilities.
An agent can perform more than one action
The operation spine does not limit agents to executing isolated commands.
It gives them reliable building blocks for multi-step work.
A user might ask:
"Add 'Follow up with design partner,' complete 'Prepare launch notes,' and then show me everything still open."
The agent can resolve that request into:
1. todo.create
2. todo.update
3. todo.listEach operation still passes through the same capability and scope checks.
The agent orchestrates the work, but it does not bypass the product.

This separation matters:
- The agent owns intent resolution and planning.
- The spine owns execution and policy.
- The interface shows the resulting state and execution trace.
Converting an existing product
This architecture does not require a wholesale rewrite.
Begin by inventorying the product verbs currently hidden inside pages, routes, services and scripts.
Choose one representative operation and migrate it completely:
- Define the contract and capability.
- Add a workflow when the operation needs coordinated steps.
- Connect the port to the existing infrastructure.
- Expose the operation through the SDK, REST, CLI and agent tool.
- Make the result visible in the live interface.
Then prove:
- An authorized principal can execute it.
- A principal without the capability is rejected.
- A principal with the wrong scope is rejected.
- SDK, REST, CLI and agent calls produce equivalent results.
- A write from a non-UI surface updates an already-open interface.
Only then migrate the next operation.
This creates the spine incrementally while preserving the working product around it.

The larger mental model
The interface is not the product.
The agent is not the product.
The API is not the product.
They are all ways of encountering the product.
The product itself is the governed set of capabilities underneath them: operations with stable identities, typed contracts, permissions, context, optional workflow orchestration and shared state.
The spine makes those capabilities consistently executable.
Agents make them easier to orchestrate.
APIs make them available to external systems.
Interfaces make the entire system visible and understandable to humans.
That is the architecture I want to keep exploring across the products I build:
"One product brain, many surfaces, and a human interface that ensures the system never disappears behind its own abstractions."