From the field #08 - AX: the agent experience your site is missing

Part 2: six portals, seventy minutes, the agent layer

Part 1 made the case that your site has a UX and no AX. The agent-discovery layer is what closes that gap. This part is the layer itself: the two manifests, what they declare, the pattern that put them on a fleet instead of one site, and the one portal that got a deliberately different answer.

I will be concrete, because the whole point of From the Field is that the files are real and live. Everything here is on production right now. You can curl it while you read.

Two manifests, one job each

The agent-discovery layer is two small public files, each aimed at one of the standards from Part 1.

/.well-known/agent.json is the A2A Agent Card. It answers “what is this agent and what can it do.” Name, description, version, the documentation URL, the capabilities it supports, how to authenticate, and a list of skills. The skills are the part that matters: each one is a named thing the agent can do, with a description an LLM reads literally to decide whether to use it, and example queries that show what it is for.

/.well-known/mcp.json is the MCP discovery manifest. It answers “where is the callable endpoint and how do I talk to it.” It points at the /mcp server, declares the transport, the auth, and which MCP primitives are available - tools, resources, prompts.

Together they are the AX equivalent of a good homepage. One says what the agent is, the other says how to connect and act. An agent that hits /.well-known/ finds both and knows, without scraping a single page, exactly what this portal offers and how to use it.

Custom per portal, because a portfolio is not a docs site

The trap here is the generic template. It is tempting to write one Agent Card, change the name, and stamp it onto every portal. That produces an agent that lies. An Agent Card whose skills do not match the real tool surface is worse than no card, because an agent will try to call a skill that is not there.

So every portal got a custom card whose skills match its actual /mcp tools verbatim. The proof is that the cards are visibly different. The portfolio agent declares seven skills - list_articles, read_article, search, list_projects, list_ecosystem, get_about, get_metrics - because that is what the portfolio’s MCP server actually exposes. The docs site for my testing pattern declares a completely different five - list_docs, read_doc, list_examples, read_example, search - because it is a different agent doing a different job. Same shape, different surface. A portfolio agent and a documentation agent are not the same agent, and their cards say so.

Each skill description is written the way I write MCP tool descriptions in general, because this is agent-SEO: the model reads the description to decide whether to call the skill. So each one is fifty words or more, says what it returns, and says when not to use it. The portfolio’s search skill tells an agent to use it for topic queries and not when it already has an exact slug, because the wrong tool choice is a wasted call. That disambiguation is the difference between an agent that uses you well and one that thrashes.

The pattern that made it a fleet, not a chore

Here is the engineering bit, and it is why six portals cost seventy minutes instead of a day.

These portals are bilingual. Two languages, built from one source into two deploys with different domains. A static public/.well-known/agent.json would have shipped the same hardcoded url to both builds, so one language would advertise the wrong domain. The fix is to not hardcode anything. Read the deploy’s own identity at build time:

// src/pages/.well-known/agent.json.ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = ({ site }) => {
  const baseUrl = site?.toString().replace(/\/$/, '') ?? '';

  const agentCard = {
    name: 'CDAT Documentation Agent',
    description: '...custom, portal-specific, 50+ words...',
    url: baseUrl,
    version: '1.0.0',
    documentationUrl: `${baseUrl}/llms.txt`,
    capabilities: {
      streaming: false,
      pushNotifications: false,
      stateTransitionHistory: false,
    },
    authentication: { schemes: ['public'], credentials: null },
    defaultInputModes: ['text/plain', 'application/json'],
    defaultOutputModes: ['text/plain', 'application/json'],
    skills: [
      /* matched to the real /mcp tools, custom per portal */
    ],
    contact: { name: 'Dariusz Kowalski', url: 'https://portfolio.sdet.it/contact' },
  };

  return new Response(JSON.stringify(agentCard, null, 2), {
    headers: { 'Content-Type': 'application/json; charset=utf-8' },
  });
};

Astro.site is read at build time, so each domain’s build bakes in its own url and its own documentation link. One source file, correct on both deploys, nothing to keep in sync. The mcp.json endpoint is the same shape. It is also the exact pattern the existing llms.txt.ts and llms-full.txt.ts endpoints already used, so the new manifests fit the repo instead of fighting it.

Note what the capabilities block honestly says: streaming off, push off, state history off. The portfolio agent is read-only and public. I did not declare a capability the endpoint does not back. An Agent Card that claims streaming it cannot do is the same lie as a skill that is not there.

The portal that got a different answer on purpose

Five of the six portals are public read - a portfolio, a docs site, the brand pages. The sixth is a private knowledge base, and it needed a more careful answer, because the right move for a private API is not to hide it from the discovery layer and it is also not to advertise its tools to anyone who asks.

So that portal declares an Agent Card too, but a deliberately vague one. Its authentication scheme is bearer, not public. Its skills are described at a high level and do not enumerate the real tool surface. The manifest’s message is precise: this brand asset exists, here is who it belongs to, and the API behind it is gated. An agent learns the asset is real and learns it needs credentials, and learns nothing it should not. Present in the discovery layer, honest about being closed. That is the auth-gated discovery answer, and it is a different decision than the five public ones, made on purpose.

What the seventy minutes actually was

The number, precisely, because precision is the brand.

I run six brand portals. The seventy minutes is the batch that brought all six to the same agent-ready baseline - one sprint, four repositories, every portal given its custom Agent Card and MCP discovery manifest, against a budget of eighty to a hundred and ten minutes.

What that number is not: it is not me inventing the whole agent layer from zero in seventy minutes. The read layer already existed. The portfolio has shipped /mcp, /llms.txt, /llms-full.txt, /rss.xml, and per-article OG images since its MVP in April. What the sprint added was the discovery layer on top - the A2A Agent Cards and the MCP discovery manifests - and the work of standardizing it across the whole fleet to a single baseline. That fleet-levelling is its own kind of work, the kind most people never budget for, and seventy minutes is the honest cost of it. The claim is the fleet claim, and I am holding it exactly.

One architectural decision underwrites the speed: per-portal, not a hub. I considered a single agent.sdet.it that namespaces every portal’s tools behind one service. I did not build it. A hub is one more service to run and one more thing to fail, and at six portals it buys nothing a per-portal card does not. Each portal carries its own manifests and is self-sufficient. The decision gets revisited at more than ten portals, not before.

One honest update, because the spec moved

I shipped all of this in May, and the agentic web does not hold still. Between that sprint and writing this, A2A reached v1.0 under the Linux Foundation and standardized the exact things this episode is about. The official Agent Card path moved to /.well-known/agent-card.json, with agent.json kept as a backward-compat alias. Auth moved from authentication.schemes to securitySchemes. Signed cards arrived as a trust model. What is live on my six portals is the May snapshot, the older agent.json shape. The clone-and-run pack I point you at in Part 3 is built to the current v1.0 spec, alias included. The portals are a snapshot in time; the pack is current. That gap is not something to hide, it is the real texture of building on a standard while it hardens, and in Part 3 it becomes one of the five lessons.

Shipping is not proof. A manifest that returns a 200 is not the same as a manifest an agent can actually read and act on. Part 3 is the verification: the twenty smoke probes I ran across the fleet, the five lessons that cost me the time so they will not cost you yours, and the pack you can clone to ship the same layer to current spec.

From the Field is what I actually build, what breaks, and what I learn. Real projects, real numbers, real bugs. No tutorials.