"""Agent cards — A2A-style capability descriptors. The Agent-to-Agent (A2A) pattern standardises how agents discover each other's capabilities. Each agent publishes a "card" that declares: name, description, IO schemas, supported intents, and escalation policy. A registry collects them; an orchestrator (here, the Coach) looks up cards rather than hardcoding agent names. This module ships the data model and an in-process registry. The same registry can later be served over HTTP at ``/.well-known/agent-card.json`` to enable cross-system collaboration. """ from __future__ import annotations from typing import Any, Dict, List, Optional from pydantic import BaseModel, Field class CapabilityIO(BaseModel): """A description of one IO surface (input shape or output shape).""" description: str json_schema: Optional[Dict[str, Any]] = None example: Optional[Dict[str, Any]] = None class Capability(BaseModel): """One thing the agent can do.""" name: str = Field(description="Stable identifier, e.g. 'assess_user'.") description: str input: CapabilityIO output: CapabilityIO side_effects: List[str] = Field( default_factory=list, description="Free-text list of memory partitions or tools the capability touches.", ) class AgentCard(BaseModel): """The card every agent publishes. Inspired by Google's A2A spec + OpenAI's plugin manifest format. """ name: str version: str = "0.1.0" description: str role: str = Field( description=( "Free-text role label, e.g. 'orchestrator', 'specialist:medical', " "'specialist:nutrition_planning'." ) ) capabilities: List[Capability] requires_human_review: bool = Field( default=False, description="True for medically sensitive specialists; gates HITL escalation.", ) contact: Optional[str] = None # --------------------------------------------------------------------------- # Registry # --------------------------------------------------------------------------- class AgentRegistry: """Trivial in-process registry. Real systems would back this with a DB.""" def __init__(self) -> None: self._cards: Dict[str, AgentCard] = {} def register(self, card: AgentCard) -> None: self._cards[card.name] = card def get(self, name: str) -> Optional[AgentCard]: return self._cards.get(name) def list(self) -> List[AgentCard]: return list(self._cards.values()) def by_role(self, role: str) -> List[AgentCard]: return [c for c in self._cards.values() if c.role == role] # --------------------------------------------------------------------------- # Default cards for every agent in this system # --------------------------------------------------------------------------- def default_cards() -> List[AgentCard]: return [ AgentCard( name="CoachAgent", description="Central orchestrator: turns user intent into a workflow of agent/tool calls.", role="orchestrator", capabilities=[ Capability( name="orchestrate", description="Plan and dispatch one action per turn until compose_response.", input=CapabilityIO(description="NutritionState (full graph state)."), output=CapabilityIO(description="Updated NutritionState with current_action set."), side_effects=["response_steps", "previous_actions"], ) ], ), AgentCard( name="MedicalAssessmentAgent", description="Produces evidence-based medical assessment + clinical flags + calculations.", role="specialist:medical", requires_human_review=True, capabilities=[ Capability( name="assess_user", description="Compute BMI/BMR/TDEE, set clinical flags, attach evidence sources.", input=CapabilityIO(description="task: str, memory: dict"), output=CapabilityIO(description="assessment_summary: str (memory side-effect: flags_and_assessments)"), side_effects=["memory.flags_and_assessments"], ) ], ), AgentCard( name="PlannerAgent", description=( "Personalised meal plans constrained by the medical assessment. " "Runs an internal deterministic check (allergy / calorie / macro " "tolerances) after the LP solver and self-revises up to twice " "before returning." ), role="specialist:nutrition_planning", capabilities=[ Capability( name="plan_meals", description=( "Draft a plan, batch-fetch nutrition facts via grounded " "WebSearchTool, run the PuLP QuantitiesFinder LP, run " "check_plan(), revise on medium/high issues, finalise." ), input=CapabilityIO(description="task: str, memory: dict"), output=CapabilityIO( description=( 'JSON envelope {"plan": {...}, "revisions": N, ' '"unresolved_issues": [...]} or {"error": "..."}' ) ), side_effects=[ "memory.plans.current_plan", "memory.plans.revision_count", "memory.plans.post_lp_issues", ], ) ], ), ] def build_default_registry() -> AgentRegistry: reg = AgentRegistry() for c in default_cards(): reg.register(c) return reg __all__ = [ "AgentCard", "AgentRegistry", "Capability", "CapabilityIO", "build_default_registry", "default_cards", ]