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-agentThe 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.cssThe 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 devIn another terminal, start the frontend:
cd frontend
npm install
npm run devThe Vite dev server proxies these paths to a2a dev:
/app/config.json
/invoke
/auth
/mcp
/.well-knownThat 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: inheritFields:
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:inheritby default, so private agents imply private apps. Useplatformfor 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 platformThe 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 deployIf 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/sessionSecurity 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.