a2a docsconceptspacked frontends

Packed frontends

Packed frontends let an agent ship with its own web app. The Python agent, its skill schemas, and a static React/Vite UI deploy together as one A2A cloud service.

Use this when the agent is more than an API:

  • a chart agent with an editor and preview pane
  • a finance agent with upload, reconciliation, approval, and export
  • a research agent with report history and artifacts
  • an internal ops agent with review queues and run receipts

The important boundary stays the same: the frontend calls the agent through the generated A2A contract. It does not receive platform secrets.

Scaffold a React app

a2a init chart-agent --frontend react
cd chart-agent

The project includes the normal agent files plus a Vite app:

chart-agent/
  agent.py
  a2a.yaml
  requirements.txt
  frontend/
    package.json
    vite.config.js
    src/
      App.jsx
      a2a.js
      main.jsx
      style.css

The generated React app loads the agent contract, lists inferred skills, shows JSON input schemas, and can invoke skills from the browser.

Run locally

Start the agent runtime:

a2a dev

In another terminal, start the frontend:

cd frontend
npm install
npm run dev

The Vite dev server proxies these paths to a2a dev:

/app/config.json
/invoke
/auth
/mcp
/.well-known

That means local React code uses the same generated contract the deployed app uses.

Manifest

Packed frontend configuration lives in a2a.yaml:

frontend:
  path: frontend
  build: npm run build
  dist: dist
  mount: /app
  auth: inherit

Fields:

  • path: frontend source directory.
  • build: optional build command. If present, deploy runs it in a Node build stage.
  • dist: directory containing the built static app.
  • mount: URL path where the app is served.
  • auth: inherit by default, so private agents imply private apps. Use platform for apps that require a logged-in A2A Cloud user.

For a prebuilt static bundle, omit build and commit frontend/dist.

Platform Auth

For a packed app with built-in A2A Cloud auth:

a2a init private-app --frontend react --auth platform

The scaffold uses PlatformUserAuth:

from a2a_pack import A2AAgent, PlatformUserAuth, RunContext, skill


class PrivateApp(A2AAgent[None, PlatformUserAuth]):
    auth_model = PlatformUserAuth

    @skill
    async def whoami(self, ctx: RunContext[PlatformUserAuth]) -> dict:
        return {
            "user_id": ctx.auth.user_id,
            "email": ctx.auth.email,
            "org": ctx.auth.org_slug,
        }

Hosted deployments verify the HttpOnly platform session cookie against the control plane before serving auth: platform frontend files or invoking PlatformUserAuth skills. Gateway-forwarded identity headers are opt-in via runtime configuration; direct deployments do not trust them by default.

Runtime contract

Every packed frontend gets generated endpoints:

Path Purpose
/app/ The bundled frontend app
/app/config.json Agent identity, endpoints, docs URL, auth mode, and skill schemas
/app/a2a-client.js Small browser helper for agent.call(...)
/.well-known/a2a-skills.json Skill names, scopes, input schemas, output schemas
/auth/session Local dev session or platform-provided user/session metadata

Example config shape:

{
  "agent": {
    "name": "chart-agent",
    "version": "0.1.0"
  },
  "endpoints": {
    "invoke": "https://chart-agent.a2acloud.io/invoke",
    "agentCard": "https://chart-agent.a2acloud.io/.well-known/agent-card.json",
    "mcp": "https://chart-agent.a2acloud.io/mcp",
    "session": "https://chart-agent.a2acloud.io/auth/session"
  },
  "skills": []
}

How skills are known

Skills are inferred from your Python agent:

from typing import Literal
from a2a_pack import A2AAgent, NoAuth, RunContext, skill


class ChartAgent(A2AAgent):
    name = "chart-agent"
    auth_model = NoAuth

    @skill(description="Render a chart from a CSV file.")
    async def render_chart(
        self,
        ctx: RunContext[NoAuth],
        dataset: str,
        chart_type: Literal["bar", "line"] = "bar",
    ) -> dict:
        return {"chart_type": chart_type, "dataset": dataset}

a2a-pack derives:

  • skill name
  • description
  • input JSON Schema
  • output JSON Schema
  • scopes
  • streaming flag
  • policy hints

The frontend reads those schemas from /app/config.json or /.well-known/a2a-skills.json.

Deploy

Deploy stays the same:

a2a deploy

If frontend.build is set, the control plane builds the frontend in a Node stage, copies frontend/dist into the agent image, and serves it from the Python A2A runtime. If frontend.build is omitted, the deploy path copies the prebuilt static bundle.

The deployed agent exposes both the app and protocol surfaces:

/app
/.well-known/agent-card.json
/.well-known/a2a-skills.json
/invoke/{skill}
/mcp
/auth/session

Security model

  • Browser code calls generated endpoints with the user session.
  • Platform secrets stay server-side.
  • Private agents should use auth: inherit, so the app follows agent access.
  • File access should go through scoped grants, signed URLs, or platform-mediated APIs.
  • The app should treat skill schemas as public contract metadata, not as secret configuration.

Current scope

Packed frontend support is static-SPA first. React/Vite works out of the box, and other static frameworks work if they build into a dist directory.

Server-rendered frameworks such as non-static Next.js or Remix need a later sidecar/container mode. For now, keep packed frontends static and let the A2A runtime handle the agent API.