--- name: a2apack-agent-authoring description: Author production a2a-pack agents with clear public @skill schemas, runtime declarations, caller-funded LLM credentials, optional agent BYOK, workspace grants, pricing, resources, and secure platform capabilities. Use when writing or reviewing agent.py for an A2A agent. --- # A2A Pack Agent Authoring An A2A agent should expose a small, stable public API and put rich reasoning inside implementation helpers, DeepAgents skills, or subagents. The public `@skill` methods are the contract shown on the Agent Card. ## Public Contract Use `A2AAgent` class attributes for runtime and marketplace behavior: - `name`, `description`, and `version` identify the agent. - `config_model` and `auth_model` define agent configuration and caller auth. - `llm_provisioning` tells the platform where LLM credentials come from. - `pricing` tells callers what they pay and whether they supply LLM creds. - `resources`, `egress`, `tools_used`, and `workspace_access` describe runtime requirements. - `wants_cp_jwt=True` is only for trusted platform agents. It forwards the caller's control-plane token. For hosted generated/user-owned agents that call an LLM, default to `LLMProvisioning.PLATFORM` with `Pricing(..., caller_pays_llm=True, ...)` so main-agent handoffs forward the caller's saved LLM credential for the callee. `LLMProvisioning.CALLER_PROVIDED` is still acceptable for explicit BYOK wording. Reserve `LLMProvisioning.PLATFORM_OR_CALLER_PROVIDED` for legacy compatibility. In every mode, read `ctx.llm`; never read `A2A_LITELLM_KEY`, `OPENAI_API_KEY`, provider keys, or platform secrets directly. If `ctx.llm.api_key` is empty, return a clear setup/config result before constructing a model or calling `create_a2a_deep_agent`. For explicitly deterministic/local-logic agents that do not call an LLM, omit the concrete class `llm_provisioning` declaration, remove unused `LLMProvisioning` imports, do not read `ctx.llm`, and set `Pricing(..., caller_pays_llm=False, ...)`. A no-LLM agent must be callable without requiring the user's LLM credential. `Pricing` only accepts `price_per_call_usd`, `caller_pays_llm`, and `notes`. Do not put `runtime.pricing.compute`, `runtime.pricing.total_usd`, `compute=`, or `total_usd=` in generated `agent.py`; the platform derives those values from declared `Resources` and billing policy. Keep each `@skill` method `async`, put `RunContext[...]` immediately after `self`, and annotate every public argument. Do not use `*args` or `**kwargs`; they are rejected and would not publish a useful schema. ## Code Example ```python from __future__ import annotations from typing import Any from pydantic import BaseModel from a2a_pack import ( A2AAgent, EgressPolicy, LLMProvisioning, NoAuth, Pricing, Resources, RunContext, WorkspaceAccess, WorkspaceMode, skill, ) class ResearchConfig(BaseModel): default_depth: int = 3 class ResearchAgent(A2AAgent[ResearchConfig, NoAuth]): name = "research-agent" description = "Researches a topic and writes a concise cited report." version = "0.1.0" config_model = ResearchConfig auth_model = NoAuth llm_provisioning = LLMProvisioning.PLATFORM pricing = Pricing( price_per_call_usd=0.05, caller_pays_llm=True, notes="Uses the caller's saved LLM credential through ctx.llm.", ) resources = Resources(cpu="1", memory="1Gi", max_runtime_seconds=900) egress = EgressPolicy(allow_hosts=("api.openai.com",)) tools_used = ("deepagents", "langchain") workspace_access = WorkspaceAccess.dynamic( max_files=128, allowed_modes=(WorkspaceMode.READ_ONLY, WorkspaceMode.READ_WRITE_OVERLAY), require_reason=False, ) @skill( description="Research a topic, save report artifacts, and return a summary", timeout_seconds=900, cost_class="llm-heavy", ) async def research( self, ctx: RunContext[NoAuth], topic: str, depth: int | None = None, save_path: str = "outputs/research-report.md", ) -> dict[str, Any]: creds = ctx.llm await ctx.emit_progress(f"researching with {creds.model}") if not creds.api_key: return { "summary": "LLM credentials were not available for this run.", "artifact": None, "path": save_path, } graph = self._build_graph(ctx) state = await graph.ainvoke( { "messages": [ { "role": "user", "content": ( f"Research {topic!r} at depth {depth or self.config.default_depth}. " f"Write the final markdown report to /workspace/{save_path}." ), } ] }, config={"recursion_limit": 500}, ) report_text = _last_message_text(state) ref = await ctx.write_artifact( "research-summary.md", report_text.encode("utf-8"), "text/markdown", ) await ctx.emit_artifact(ref) return {"summary": report_text, "artifact": ref.uri, "path": save_path} ``` ## Schema Rules Public arguments become the live A2A input schema. Prefer plain JSON-shaped types: `str`, `int`, `float`, `bool`, `list[str]`, `dict[str, Any]`, or Pydantic models. Provide defaults for optional knobs. Avoid exposing internal implementation details such as model names, temporary paths, prompt fragments, or unbounded command strings. ## Runtime Rules If a skill calls `ctx.workspace_backend()` or `ctx.workspace`, declare `workspace_access`. If it writes user-visible files, also emit artifacts with `ctx.write_artifact` and `ctx.emit_artifact`. If a skill runs code, renderers, converters, or data tools that create downloadable files, run them through `await ctx.workspace_shell(...)` or `await ctx.workspace_python(...)`. The sandbox persists `/workspace` writes directly and mirrors changed files elsewhere in the guest rootfs under `outputs/rootfs-captures/...`. In-process subprocesses write inside the agent pod, not the caller's mounted or rootfs-captured workspace. When invoking an inner DeepAgents graph, pass `config={"recursion_limit": 500}` to `graph.ainvoke(...)` or `graph.astream_events(...)`. Complex skill/subagent workflows can exceed LangGraph defaults during normal operation. If an agent needs system binaries, mirror the need in `a2a.yaml`: ```yaml runtime: apt_packages: [ffmpeg, poppler-utils] resources: cpu: "2" memory: 2Gi max_runtime_seconds: 900 ``` If it calls external hosts, declare `egress` on the class. If it needs secrets, use `required_secrets` and `ctx.secret("NAME")`; do not hard-code credentials. ## Packed Frontends If the user asks for a usable app, dashboard, workflow UI, or customer-facing demo surface, scaffold a packed frontend instead of a separate web app. Use `init_agent_template(name, description, frontend="react")` for the React/Vite starter or `frontend="static"` for a no-build HTML bundle. The deployment manifest should keep the browser app under the agent source: ```yaml frontend: path: frontend build: npm run build dist: dist mount: /app auth: inherit ``` At runtime the platform serves the app from `/app`, exposes generated config at `/app/config.json`, exposes a helper client at `/app/a2a-client.js`, and uses the agent's public `@skill` schemas for callable operations. Frontend code should load the generated config/client, call the public skills, and rely on inherited platform auth. Do not put control-plane tokens, provider keys, secrets, internal stack details, or private execution assumptions in frontend source. ## A2A Skill vs DeepAgents Skill Use A2A `@skill` for caller-visible operations and billing boundaries. Use DeepAgents skills for internal procedural knowledge the LLM should apply. A generated project can have one public `@skill` that invokes a DeepAgent loaded with several internal `skills//SKILL.md` bundles.