Dev.to AI 🤖 Ai 👁 0 📖 10 min read

AI Won’t Replace You. It’s Moving Your Code From the Screen to the Contract

Every few weeks, someone posts an impressive agent demo, and the replies fill up with the same prediction: developers are done. Teams will shrink to one person and a model, and that one person will be cheap. I'm a front

Every few weeks, someone posts an impressive agent demo, and the replies fill up with the same prediction: developers are done. Teams will shrink to one person and a model, and that one person will be cheap.

I'm a frontend developer, mostly Angular, and I'm genuinely excited about what agents can do. Recently, I watched a livestream where someone used CopilotKit to build an app that turns plain prompts into charts. Impressive stuff. But the question that stuck with me wasn't "is my job over?" It was: who decided which charts it was allowed to draw?

I'm also tired of the "devs are done" story. Not because I think nothing will change. A lot will. But when I stopped reading hot takes and started reading the specs behind the agent stack, I found something that the crowd keeps skipping: these protocols create a lot of engineering work. It just isn't the work we're used to.

This week gave me a good excuse to write it down. On November 11, Google shipped A2UI 1.0, the first stable version of its open protocol for agent-generated UI. Atlassian is bringing it to Jira, and Bloomberg and ByteDance are building with it. Its sibling, A2A, the protocol agents use to talk to each other, reached v1.0 earlier this year under the Linux Foundation.

Here's my argument in one line: these protocols don't remove the code. They move it from the screen to the contract.

The map: four boundaries, four protocols

"Agentic app" sounds like one thing. In practice, it's a set of boundaries, and each one now has its own protocol:

Boundary Protocol What crosses it Who designs it
Agent ↔ agent A2A tasks, messages, artifacts backend
Agent ↔ tools and data MCP tool calls, resources backend
Agent ↔ app AG-UI events, state, user actions full-stack
Agent → screen A2UI a declarative description of UI frontend

A quick tour:

  • A2A lets an agent discover another agent through an Agent Card, a JSON document published at /.well-known/agent-card.json. It can then hand that agent a task and follow it through a lifecycle: submitted, working, input-required, auth-required, completed, failed, and so on. It runs over JSON-RPC, gRPC, or plain HTTP+JSON.
  • MCP is how an agent reaches tools and data. You probably know this one already.
  • AG-UI, from the CopilotKit team, is the event stream between your app and an agent backend: messages, shared state, tool calls and user actions.
  • A2UI is the new piece. The agent doesn't send HTML or JavaScript. It sends JSON that says "put a chart here, bound to this data", and your app renders it with your components. That JSON can travel over AG-UI, over A2A (as an extension) or inside MCP.

Look at the last column of the table. Every row has an owner, and none of them is "the model".

Frontend: from screens to vocabulary

This is the part of A2UI that changed my mind. An agent can only use components that exist in a catalog, and the catalog belongs to the client. The A2UI basic catalog gives you primitives like Text, Row, Column, Button and TextField. Anything more specific, like a chart in your brand colours that handles empty states and keyboard navigation, is a component you write and register.

That turns the catalog into something new: a public API whose consumer is a language model. Everything you'd do for a good component library still applies, plus some new questions:

  • What is the agent allowed to ask for? A2UI catalogs can restrict which components nest inside which (allowedChildren, allowedParents) and which side may call which function (allowedCallers). That isn't styling. It's a security boundary, and the frontend team owns it.
  • How do you describe a component so a model uses it well? Catalogs carry their own guidance for the model. Naming props and documenting them becomes a form of prompt design.
  • How do you guarantee quality you can't review screen by screen? When layouts are assembled at runtime, accessibility, loading and error states, and performance budgets can't be checked in a pull request for each screen. They have to live inside the components.

Even the vendors mix approaches. In CopilotKit's open-source Open Intelligent UI, simple tables are rendered through A2UI, while charts, maps and calculators go through a separate path that streams generated UI into a sandboxed iframe. Someone had to decide which kind of UI each moment deserves and what risk each option carries. That decision is architecture, and no model made it for them.

So, back to my livestream question: who decides which charts an agent can draw? With A2UI, whoever owns the catalog. With an open-ended iframe, the model does, but inside a sandbox that a person designed. Either way, a developer made the call.

Backend: from endpoints to capabilities

On the backend, A2A changes the unit of design. You're no longer just exposing GET /expenses. You're publishing capabilities, listed as skills in an Agent Card, that other agents will discover and call, possibly from other teams or other companies.

The hard problems don't go away. They turn into distributed-systems problems:

  • Long-running work. A task can pause in input-required while it waits for a human, or in auth-required while it waits for credentials, and then resume. Someone has to model those states, the timeouts and the retries.
  • Trust. Which agents may call yours, and how do you know an agent is who it claims to be? A2A supports OAuth 2.0, OpenID Connect, mutual TLS, and signed Agent Cards. Choosing and wiring those is real work, and getting it wrong is real exposure.
  • Keeping data away from the model. A2UI 1.0 can bind data on the server, so sensitive values reach the screen without ever entering the model's context. Deciding what the model sees, and what it never sees, is a design decision with legal consequences.
  • Observability. When one user request hops across three agents, and one of them misbehaves, someone has to trace it.

None of this is "write less code and think about product instead". It's harder code, in places where architecture and product decisions can't be separated.

Where this doesn't fit, and what really goes away

I'd be selling hype myself if I stopped there, so here's the honest part.

  • Some work does shrink. Hand-building yet another CRUD screen, filter panel or settings form: much of that will be composed by agents from good catalogs. If that's the only thing you do, your job is changing.
  • Generative UI isn't always better. A checkout, a regulated form, anything where users rely on muscle memory: those want a predictable interface. Sonu Kapoor argues in InfoWorld that generative UI needs component systems rather than code generated at runtime, and there are thoughtful posts on why generative UI doesn't make sense for many startups and on what actually works in production.
  • It's early. A2UI 1.0 is days old. Some renderers were still on v0.9.x when it was announced, and the tooling moves fast: CopilotKit archived its generative UI playground repo in October.

Look closely at those objections, though. Each one is an argument for more engineering judgement, not less: knowing when generative UI is the wrong tool, building the components it depends on, and deciding where the boundaries go.

A concrete example: "Show me travel spend by person"

Here's a small scenario that puts all the pieces together. I'll build it end to end in Part 2.

Picture a team expenses app. The user types: "Show me this quarter's travel spend, by person."

  1. An orchestrator agent receives the request. It doesn't own finance data, so it delegates the task through A2A to a finance agent, which could be built by another team or another company.
  2. The finance agent answers with A2UI: a card with a bar chart.
  3. The bar chart isn't in the basic catalog. It's a component the frontend team added to the app's own catalog.
  4. When the user changes the period, that action goes back to the agent and the chart updates.
  5. The amounts reach the chart through data binding, not through the model's text.
  6. And the form to submit a new expense? A plain Angular reactive form. Predictable, validated, and boring, on purpose.

Here's the finance agent's Agent Card, trimmed. The A2UI extension tells other agents which UI vocabularies this agent can speak:

{
  "name": "Finance Agent",
  "description": "Answers questions about team expenses and budgets.",
  "version": "1.0.0",
  "supportedInterfaces": [
    {
      "url": "https://finance.example.com/a2a",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0"
    }
  ],
  "capabilities": {
    "streaming": true,
    "extensions": [
      {
        "uri": "https://a2ui.org/a2a-extension/a2ui/v1.0",
        "required": false,
        "params": {
          "supportedCatalogIds": ["https://example.com/a2ui/expenses-catalog.json"]
        }
      }
    ]
  },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "skills": [
    {
      "id": "spend-report",
      "name": "Spend report",
      "description": "Summarises spend by person, category or period.",
      "tags": ["finance", "reporting"]
    }
  ]
}

What the finance agent sends back isn't HTML. It's a short stream of A2UI messages that describe the UI:

{"version": "1.0", "createSurface": {"surfaceId": "travel_spend", "catalogId": "https://example.com/a2ui/expenses-catalog.json"}}
{"version": "1.0", "updateComponents": {"surfaceId": "travel_spend", "components": [
  {"id": "root", "component": "Column", "children": ["title", "chart"]},
  {"id": "title", "component": "Text", "text": "Travel spend, Q4"},
  {"id": "chart", "component": "BarChart", "chartData": {"@path": "/spend"}}
]}}
{"version": "1.0", "updateDataModel": {"surfaceId": "travel_spend", "path": "/spend", "value": [
  {"label": "Ana", "value": 1840},
  {"label": "Rui", "value": 960}
]}}

Three details are worth noticing:

  • The components are a flat list with IDs, not a nested tree. That suits a model that streams tokens: pieces can arrive in any order, and the renderer waits until a root exists before drawing.
  • The layout and the data travel separately. The model decides what to show. The numbers go into a data model that, in a real setup, the agent's server can fill straight from the finance system.
  • BarChart only works because someone built it. That someone is the frontend team, and this is what their side looks like in Angular:
// bar-chart.ts (simplified from the official A2UI "authoring components" guide)
import { Component, computed } from '@angular/core';
import { AngularCatalog, BASIC_COMPONENTS, BASIC_FUNCTIONS, CatalogComponent } from '@a2ui/angular/v0_9';
import { ComponentApi } from '@a2ui/web_core/v0_9';
import { BaseChartDirective } from 'ng2-charts';
import { z } from 'zod';

// 1. The contract: what the agent can ask for.
export const BarChartApi = {
  name: 'BarChart',
  schema: z.object({
    title: z.string().optional(),
    chartData: z.array(z.object({ label: z.string(), value: z.number() })),
  }).strict(),
} satisfies ComponentApi;

// 2. The implementation: your design system, your accessibility rules.
@Component({
  selector: 'app-bar-chart',
  imports: [BaseChartDirective],
  template: `<canvas baseChart type="bar" [data]="chartData()"></canvas>`,
})
export class BarChart extends CatalogComponent<typeof BarChartApi> {
  protected readonly chartData = computed(() => {
    const rows = this.props()['chartData']?.value() ?? [];
    return {
      labels: rows.map((r) => r.label),
      datasets: [{ data: rows.map((r) => r.value) }],
    };
  });
}

// 3. The vocabulary: everything an agent can render in this app.
export const EXPENSES_CATALOG = new AngularCatalog(
  'https://example.com/a2ui/expenses-catalog.json',
  [...BASIC_COMPONENTS, { ...BarChartApi, component: BarChart }],
  BASIC_FUNCTIONS,
);

That BarChartApi schema is the contract. The agent can't ask for a PieChart nobody built. It can't pass a prop nobody declared, thanks to .strict(). When it gets something wrong, the renderer rejects the message and reports an error back to the agent. The model writes the request, but the frontend team decides what's possible.

A note on versions: these snippets use the Angular renderer's current v0_9 entry point. Renderer packages for 1.0 are rolling out, so check the A2UI docs before you copy anything.

What to learn next

If you're on the frontend:

  • Treat your design system as an API: strict prop contracts, schemas, and documentation written for a model as well as for people.
  • Pick one A2UI renderer (Angular, React, Lit or Flutter) and build one custom catalog component end to end.
  • Put accessibility and every state (loading, empty, error) inside your components, because nobody will review each generated screen.

If you're on the backend:

  • Read the task lifecycle and security sections of the A2A spec. Model a task that pauses for human input and resumes.
  • Learn the auth options (OAuth 2.0, OpenID Connect, mutual TLS) and what signed Agent Cards protect against.
  • Practise drawing the line between data the model sees and data it never touches.

For both: learn to choose. A predefined component, a declarative A2UI surface, a sandboxed open-ended UI, or no generative UI at all. Making that call well, for each moment of a product, is the job.

The job didn't disappear. It moved.

The "AI will replace developers" story assumes our job is typing code into screens. Read A2A and A2UI closely, and you see the opposite. Agents are only as good as the contracts, catalogs, and boundaries someone designs for them. That someone needs product sense and architectural judgement, and still has to write code very well.

If you've already shipped generative UI, I'd love to hear what broke and what surprised you, in the comments.

Further reading

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.