Skip to content

Adapting an existing agent to A2A

super a2a adapt takes an agent that already exists in your codebase and produces an A2A 1.0 Agent Card and a server for it. Your agent is not modified, imported into a SuperOptiX project, or rewritten as a SuperSpec playbook.

Use this when you have agents built before you adopted SuperOptiX and want them reachable by other agents, inside or outside your organisation.

Quick start

super a2a adapt --entrypoint mycrew:crew --framework crewai

The command imports mycrew, reads the crew attribute, derives the skills a calling agent would route on, and writes three files to ./a2a:

File Purpose
agent-card.json A2A 1.0 Agent Card, advertising both the 1.0 and 0.3 spec lines
a2a_server.py ASGI application that imports your entrypoint and serves it
agentspec.json The generated intermediate representation (see below)

Run the server with any ASGI host:

uvicorn a2a.a2a_server:app --port 8000
curl localhost:8000/.well-known/agent-card.json

Options

Flag Default Meaning
--entrypoint required Where your agent lives, as module:attribute
--framework detected Framework of the agent
--out a2a Directory for the generated files
--url http://127.0.0.1:8000 Public URL the card advertises
--project-root current directory Directory to import your agent from

--framework is optional. When omitted, each registered introspector is asked whether it recognises the loaded object. Pass it explicitly if detection fails or if you have wrapped the agent in something unusual.

--url must match the address the agent is actually reachable at. A card whose advertised URL does not resolve is worse than no card: other agents discover it, call it, and record the failure against your agent.

Entrypoints

The entrypoint is module:attribute, resolved against --project-root. Attribute paths may be nested:

--entrypoint mycrew:crew
--entrypoint app.agents.support:triage_crew
--entrypoint app.rag:program

A zero-argument factory function is called, so both of these work:

crew = Crew(agents=[...], tasks=[...])       # --entrypoint mymod:crew

def build_crew():                             # --entrypoint mymod:build_crew
    return Crew(agents=[...], tasks=[...])

Agent objects are never called during introspection, even when they are callable. A DSPy module accepts **kwargs and would run if invoked, which would require a configured language model to read a card. Reading an agent's shape has no side effects.

Supported frameworks

Skills come from whichever part of each framework names a capability.

Framework --framework Skills derived from
CrewAI crewai Crew tasks; agent roles when there are no tasks
DSPy dspy Signature instructions, input and output fields
OpenAI Agents SDK openai Tool names and descriptions
Pydantic AI pydantic-ai Function toolset entries
Google ADK google-adk Agent description, plus sub-agents
Microsoft Agent Framework microsoft Agent description and instructions
Claude Agent SDK claude-sdk AgentDefinition.description, or ClaudeAgentOptions.system_prompt
DeepAgents deepagents Subagent names and descriptions

Notes per framework

CrewAI. A task is the closest thing CrewAI has to a named capability, so tasks become skills and expected_output is appended to the description. A crew with no tasks falls back to its agents' roles and goals.

DSPy. A signature already names its inputs and outputs with per-field descriptions, and its docstring is the task instruction. The generated description states the instruction, then what the skill takes and returns. DSPy's own reasoning and rationale fields are excluded; they are internal scratchpads rather than part of the declared interface.

Google ADK. The card uses description, not instruction. An instruction steers the model ("Be concise"); a description tells other agents what the agent does. Sub-agents are added as separate skills, since they are individually routable.

DeepAgents. create_deep_agent returns a compiled LangGraph, which says little about capability on its own. Subagents are the readable surface. A graph whose subagents carry no descriptions cannot be adapted, and the command says so rather than emitting an empty card.

The generated intermediate representation

agentspec.json is a SuperSpec, generated rather than authored:

{
  "apiVersion": "agent/v1",
  "kind": "AgentSpec",
  "metadata": {
    "name": "support-crew",
    "framework": "crewai",
    "entrypoint": "mycrew:crew"
  },
  "spec": { "skills": [ ... ] },
  "optimizable": ["skills[].description", "skills[].examples"]
}

It exists for three reasons.

Each framework needs one introspector and shares every emitter, so adding a framework does not multiply the work of producing cards, servers or version bridging.

It is inspectable. If a skill was read badly, you can see exactly how before anything is published, and correct it.

The optimizable field records which parts of the card may be rewritten by optimisation. Skill descriptions and examples are the routing interface other agents read. Identity and protocol fields are not optimisable, so an optimiser cannot change what your agent claims to be. See Routing quality.

What the generated server does

a2a_server.py imports your entrypoint lazily on first request and bridges A2A messages onto each framework's own entry API: Crew.kickoff for CrewAI, the signature's first input field for DSPy, Runner.run for the OpenAI Agents SDK, InMemoryRunner for Google ADK, and so on.

Multi-field results are labelled rather than serialised raw. A DSPy prediction returning category and reply is rendered as two labelled lines; a single output field is returned on its own. Internal fields are dropped.

The file is generated once and then belongs to you. Edit it, commit it, deploy it. Re-running adapt overwrites it, so move custom logic elsewhere or write to a different --out.

Verifying an adapted agent

Adapted agents are conformance-tested with the same suite as SuperOptiX's own published endpoint. See A2A conformance for running the Technology Compatibility Kit against one.

Limitations

Introspection reads structure, not behaviour. A skill described vaguely in the source produces a vaguely described skill in the card. Measuring that is what Routing quality is for.

Streaming is single-turn: the generated runtime yields one result rather than incremental output. Frameworks that stream natively are not yet mapped onto A2A streaming events.

Push notifications are not supported. The card declares pushNotifications: false, and the configuration methods return PushNotificationNotSupportedError rather than a 404.