162 lines
5.5 KiB
Markdown
162 lines
5.5 KiB
Markdown
---
|
|
name: a2apack-agent-authoring
|
|
description: Author production a2a-pack agents with clear public @skill schemas, runtime declarations, caller-provided LLMs, 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.
|
|
|
|
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.CALLER_PROVIDED
|
|
pricing = Pricing(
|
|
price_per_call_usd=0.05,
|
|
caller_pays_llm=True,
|
|
notes="Caller supplies LLM credentials through the platform.",
|
|
)
|
|
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}")
|
|
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(...)` and make the command write under
|
|
`/workspace/outputs/...`. In-process subprocesses write inside the agent pod,
|
|
not the caller's mounted 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.
|
|
|
|
## 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.
|