Extending A2A Agent Cards¶
This guide explains the A2A (Agent-to-Agent) Agent Card data model, how FCC personas map to Agent Cards, and how to extend cards with domain-specific capabilities, custom metadata, and schema validation.
Table of Contents¶
- Agent Card Structure
- Data Model Reference
- R.I.S.C.E.A.R. to Agent Card Mapping
- Building Cards from Personas
- Adding Domain-Specific Capabilities
- Custom Metadata
- Schema Validation
- Round-Trip Serialization
- Testing
- Related Documentation
Agent Card Structure¶
An A2A Agent Card is a self-describing manifest that advertises an agent's identity, capabilities, and skills. In FCC, every persona can be represented as an Agent Card, enabling cross-project discovery and interoperability.
The card structure follows the Google A2A specification with FCC-specific
extensions stored in the metadata field.
AgentCard
+-- id (string) Persona ID, e.g. "RC"
+-- name (string) Human-readable name
+-- description (string) Derived from R.I.S.C.E.A.R. role
+-- version (string) FCC package version
+-- url (string) Agent endpoint URL
+-- endpoint (AgentEndpoint)
| +-- url (string)
| +-- protocol (string) default "a2a/v1"
+-- capabilities (tuple[AgentCapability, ...])
| +-- name (string) Slugified capability name
| +-- description (string) Full description
| +-- tags (tuple[str, ...])
+-- skills (tuple[SkillDefinition, ...])
| +-- id (string) e.g. "scaffold_RC"
| +-- name (string) Human-readable
| +-- description (string) Action description
| +-- input_schema (dict) JSON Schema for inputs
| +-- output_schema (dict) JSON Schema for outputs
| +-- tags (tuple[str, ...])
+-- metadata (dict)
+-- category (string)
+-- fcc_phase (string)
+-- role_title (string)
+-- champion_of (string, optional)
+-- orchestrates (list[str], optional)
Data Model Reference¶
All models are frozen dataclasses defined in src/fcc/protocols/a2a/models.py.
AgentEndpoint¶
from fcc.protocols.a2a.models import AgentEndpoint
endpoint = AgentEndpoint(
url="http://localhost:8080/agents/rc",
protocol="a2a/v1",
)
AgentCapability¶
from fcc.protocols.a2a.models import AgentCapability
cap = AgentCapability(
name="code_review",
description="Systematic code review with quality assessment",
tags=("role_skill",),
)
SkillDefinition¶
from fcc.protocols.a2a.models import SkillDefinition
skill = SkillDefinition(
id="scaffold_RC",
name="Scaffold (Research Coordinator)",
description="Generate project scaffolding for research coordination",
input_schema={"inputs": ["project_name", "domain"]},
output_schema={"outputs": ["scaffold_structure"]},
tags=("scaffold", "core"),
)
AgentCard¶
from fcc.protocols.a2a.models import AgentCard
card = AgentCard(
id="RC",
name="Research Coordinator",
description="Coordinates cross-functional research activities",
version="1.0.1",
url="http://localhost:8080/agents/rc",
endpoint=endpoint,
capabilities=(cap,),
skills=(skill,),
metadata={"category": "core", "fcc_phase": "find"},
)
R.I.S.C.E.A.R. to Agent Card Mapping¶
The CardBuilder in src/fcc/protocols/a2a/card_builder.py maps R.I.S.C.E.A.R.
persona fields to Agent Card fields as follows:
| R.I.S.C.E.A.R. Field | Agent Card Field | Notes |
|---|---|---|
persona.id |
card.id |
Direct mapping |
persona.name |
card.name |
Direct mapping |
persona.riscear.role |
card.description |
Role description becomes card description |
persona.riscear.archetype |
card.capabilities[0] |
Primary capability tagged "archetype" |
persona.riscear.role_skills |
card.capabilities[1..] |
Additional capabilities tagged "role_skill" |
WorkflowAction (per persona) |
card.skills |
One skill per action type |
persona.category |
card.metadata["category"] |
Stored in metadata |
persona.fcc_phase |
card.metadata["fcc_phase"] |
Stored in metadata |
persona.role_title |
card.metadata["role_title"] |
Stored in metadata |
persona.champion_of |
card.metadata["champion_of"] |
Only if set |
persona.orchestrates |
card.metadata["orchestrates"] |
Only if non-empty |
Building Cards from Personas¶
Single Persona¶
from fcc.personas.registry import PersonaRegistry
from fcc.protocols.a2a.card_builder import CardBuilder
registry = PersonaRegistry.from_yaml_directory("src/fcc/data/personas")
persona = registry.get("RC")
builder = CardBuilder(base_url="https://agents.example.com")
card = builder.build_card(persona)
print(card.id) # "RC"
print(card.description) # The R.I.S.C.E.A.R. role text
print(len(card.capabilities)) # archetype + role_skills count
All Personas (with actions)¶
from fcc.workflow.actions import WorkflowActionRegistry
action_registry = WorkflowActionRegistry.from_yaml_directory(
"src/fcc/data/personas/actions"
)
cards = builder.build_all_cards(registry, action_registry=action_registry)
print(f"Built {len(cards)} cards") # 102 cards
Writing .well-known/agent.json¶
from pathlib import Path
output_path = CardBuilder.write_well_known(card, Path("output"))
# Writes to output/.well-known/agent.json
Adding Domain-Specific Capabilities¶
To add capabilities beyond what R.I.S.C.E.A.R. provides, build the card and
then create a new card with additional capabilities. Since AgentCard is
frozen, use dataclasses.replace:
from dataclasses import replace
from fcc.protocols.a2a.models import AgentCapability
custom_cap = AgentCapability(
name="tmf_federation",
description="Federate knowledge via TMF graph queries",
tags=("domain", "knowledge_graph"),
)
extended_card = replace(
card,
capabilities=card.capabilities + (custom_cap,),
)
To add a custom skill:
from fcc.protocols.a2a.models import SkillDefinition
custom_skill = SkillDefinition(
id="federate_query_RC",
name="Federate Query (Research Coordinator)",
description="Execute a federated knowledge graph query",
input_schema={
"type": "object",
"properties": {
"query": {"type": "string"},
"sources": {"type": "array", "items": {"type": "string"}},
},
"required": ["query"],
},
output_schema={
"type": "object",
"properties": {
"results": {"type": "array"},
"source_count": {"type": "integer"},
},
},
tags=("federation", "knowledge_graph"),
)
extended_card = replace(
card,
skills=card.skills + (custom_skill,),
)
Custom Metadata¶
The metadata dict on AgentCard is unstructured -- you can add any
JSON-serializable key-value pairs. The CardBuilder populates standard
FCC metadata automatically; add domain-specific keys after building.
extended_metadata = dict(card.metadata)
extended_metadata["deployment_region"] = "us-east-1"
extended_metadata["max_concurrency"] = 10
extended_metadata["compliance_tags"] = ["SOC2", "GDPR"]
extended_card = replace(card, metadata=extended_metadata)
When serializing to JSON, all metadata keys are preserved:
Schema Validation¶
Every Agent Card can be validated against the JSON Schema at
src/fcc/data/schemas/a2a_card.schema.json.
Programmatic Validation¶
errors = card.validate_against_schema()
if errors:
for err in errors:
print(f"Validation error: {err}")
else:
print("Card is valid")
The validate_against_schema method uses jsonschema.Draft7Validator
internally and returns a list of error message strings.
Schema Location¶
The schema file is resolved via fcc._resources.get_schema_path("a2a_card").
It defines the required fields (id, name, description, version,
url, endpoint) and the structures for capabilities, skills, and metadata.
Round-Trip Serialization¶
All models support full round-trip serialization:
# PersonaSpec -> AgentCard -> dict -> JSON -> dict -> AgentCard
card = builder.build_card(persona)
card_dict = card.to_dict()
card_json = card.to_json(indent=2)
restored_dict = json.loads(card_json)
restored_card = AgentCard.from_dict(restored_dict)
assert restored_card.id == card.id
assert restored_card.name == card.name
assert len(restored_card.capabilities) == len(card.capabilities)
assert len(restored_card.skills) == len(card.skills)
Nested objects (AgentEndpoint, AgentCapability, SkillDefinition) are
recursively serialized and deserialized.
Testing¶
Validate All 102 Cards¶
def test_all_cards_valid():
registry = PersonaRegistry.from_yaml_directory("src/fcc/data/personas")
builder = CardBuilder()
cards = builder.build_all_cards(registry)
assert len(cards) == 102
for card in cards:
errors = card.validate_against_schema()
assert errors == [], f"Card {card.id} has errors: {errors}"
Check Skill Counts¶
def test_cards_with_actions_have_skills():
registry = PersonaRegistry.from_yaml_directory("src/fcc/data/personas")
action_registry = WorkflowActionRegistry.from_yaml_directory(
"src/fcc/data/personas/actions"
)
builder = CardBuilder()
cards = builder.build_all_cards(registry, action_registry=action_registry)
cards_with_skills = [c for c in cards if c.skills]
assert len(cards_with_skills) > 0
Round-Trip Consistency¶
def test_card_round_trip():
builder = CardBuilder()
card = builder.build_card(persona)
restored = AgentCard.from_dict(card.to_dict())
assert restored.id == card.id
assert restored.endpoint.url == card.endpoint.url
assert len(restored.capabilities) == len(card.capabilities)
Related Documentation¶
- Protocol Bridge Patterns -- routing protocol messages
- Custom MCP Tools -- MCP tool and resource definitions
- WebSocket Architecture -- real-time event delivery
- Understanding the FCC Ecosystem -- ecosystem overview
- Ecosystem Governance Patterns -- JV partnerships