This repository has been archived on 2026-06-28. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
agent-builder/agent_builder/skills/a2apack-agent-authoring/SKILL.md
robert 64fd7c26f2
All checks were successful
build / build (push) Successful in 17s
Require user LLM credentials
2026-06-06 12:21:48 -03:00

7.7 KiB

name, description
name description
a2apack-agent-authoring 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 ChatOpenAI.

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

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:

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:

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/<name>/SKILL.md bundles.