Skip to main content

A2A Lite — Python

PyPI GitHub

A2A Lite is designed for learning and prototyping. It's the friendly on-ramp to Google's A2A Protocol — get familiar with agent-to-agent concepts with minimal boilerplate before diving into the full google/a2a-python SDK. Perfect for courses, POCs, and demos.

Build A2A agents in 8 lines. Add features when you need them.

Wraps the official A2A Python SDK with a simple, decorator-based API. 100% protocol-compatible.

v1.0.1 — A2A Protocol v1.0. Serves A2A v1.0 on a2a-sdk >= 1.1.2 (JSON-RPC + REST transports, card at /.well-known/agent-card.json). No v0.3 compatibility. See MIGRATION.md.

from a2a_lite import Agent

agent = Agent(name="Bot", description="My bot")

@agent.skill("greet")
async def greet(name: str) -> str:
    return f"Hello, {name}!"

agent.run()

Installation

pip install a2a-lite
# or
uv add a2a-lite

Requirements: Python 3.10+ (pulls in a2a-sdk[http-server] >= 1.1.2 automatically)


Quick Start

1. Create an agent

from a2a_lite import Agent

agent = Agent(name="Calculator", description="Does math")

@agent.skill("add")
async def add(a: int, b: int) -> int:
    return a + b

@agent.skill("multiply")
async def multiply(a: int, b: int) -> int:
    return a * b

agent.run(port=8787)

2. Test it (no HTTP needed)

from a2a_lite import Agent, AgentTestClient

agent = Agent(name="Calculator", description="Does math")

@agent.skill("add")
async def add(a: int, b: int) -> int:
    return a + b

client = AgentTestClient(agent)
result = client.call("add", a=2, b=3)
assert result == 5

3. Call it over the network

curl -X POST http://localhost:8787/ \
  -H "Content-Type: application/json" \
  -H "A2A-Version: 1.0" \
  -d '{
    "jsonrpc": "2.0",
    "method": "SendMessage",
    "id": "1",
    "params": {
      "message": {
        "role": "ROLE_USER",
        "parts": [{"text": "{\"skill\": \"add\", \"params\": {\"a\": 2, \"b\": 3}}"}],
        "messageId": "msg-1"
      }
    }
  }'

Progressive Complexity

Level 1 — Basic Skills

from a2a_lite import Agent

agent = Agent(name="Bot", description="A bot")

@agent.skill("greet")
async def greet(name: str) -> str:
    return f"Hello, {name}!"

agent.run()

Level 2 — Pydantic Models

Pass dicts from callers — they're auto-converted to Pydantic models:

from pydantic import BaseModel

class User(BaseModel):
    name: str
    email: str

@agent.skill("create_user")
async def create_user(user: User) -> dict:
    return {"id": 1, "name": user.name}

Lists of models work too:

from typing import List

@agent.skill("count_users")
async def count_users(users: List[User]) -> int:
    return len(users)

Level 3 — Streaming

Just yield instead of return:

@agent.skill("chat", streaming=True)
async def chat(message: str):
    for word in message.split():
        yield word + " "

Level 4 — Middleware

Cross-cutting concerns without touching skill code:

@agent.middleware
async def log_requests(ctx, next):
    print(f"Calling: {ctx.skill}")
    result = await next()
    print(f"Result: {result}")
    return result

Built-in middleware:

from a2a_lite import logging_middleware, timing_middleware, retry_middleware, rate_limit_middleware

agent.add_middleware(logging_middleware)
agent.add_middleware(timing_middleware)
agent.add_middleware(rate_limit_middleware(max_per_minute=60))
agent.add_middleware(retry_middleware(max_retries=3))

Level 5 — File Handling

Accept and return files through the A2A protocol:

from a2a_lite import FilePart

@agent.skill("summarize")
async def summarize(doc: FilePart) -> str:
    content = await doc.read_text()
    return f"Summary: {content[:100]}..."

Level 6 — Task Tracking

Long-running operations with progress updates:

from a2a_lite import TaskContext

agent = Agent(name="Bot", description="A bot", task_store="memory")

@agent.skill("process")
async def process(data: str, task: TaskContext) -> str:
    await task.update("working", "Starting...", progress=0.0)
    for i in range(10):
        await task.update("working", f"Step {i}/10", progress=i/10)
    return "Done!"

Level 7 — Authentication

API keys are hashed in memory using SHA-256 — plaintext keys are never stored.

from a2a_lite import Agent, APIKeyAuth

agent = Agent(
    name="SecureBot",
    description="A secure bot",
    auth=APIKeyAuth(keys=["secret-key-1", "secret-key-2"]),
)

Other auth providers:

from a2a_lite.auth import BearerAuth, OAuth2Auth

# Bearer/JWT
agent = Agent(
    name="Bot", description="A bot",
    auth=BearerAuth(secret="your-jwt-secret"),
)

# OAuth2 (requires: pip install a2a-lite[oauth])
agent = Agent(
    name="Bot", description="A bot",
    auth=OAuth2Auth(issuer="https://auth.example.com", audience="my-api"),
)

Skills can receive auth results by type-hinting a parameter as AuthResult:

from a2a_lite.auth import AuthResult

@agent.skill("whoami")
async def whoami(auth: AuthResult) -> dict:
    return {"user": auth.identity, "scheme": auth.scheme}

Level 8 — CORS & Production Mode

agent = Agent(
    name="Bot",
    description="A bot",
    cors_origins=["https://myapp.com", "https://admin.myapp.com"],
    production=True,  # Warns if running over HTTP
)

Level 10 — Multi-Agent Communication

TaskHandle — track remote tasks

from a2a_lite import Agent, AgentNetwork, TaskHandle

network = AgentNetwork()
network.add("data", "http://localhost:8787")

agent = Agent(name="Orchestrator", network=network)

@agent.skill("process")
async def process(query: str) -> str:
    # Get a TaskHandle instead of just the result
    handle = await agent.delegate("data", "fetch", query=query, return_handle=True)
    print(f"Task ID: {handle.task_id}")
    print(f"Agent: {handle.agent_url}")
    print(f"Result: {handle.result}")
    return str(handle.result)

Task Lifecycle — poll and cancel remote tasks

Use convenience methods directly on the handle, or via the network by name:

# Poll status directly on the handle
status = await handle.get_status()
status = await handle.get_status(timeout=15.0)

# Or poll via network name
status = await network.get_task("data", handle.task_id)
status = await network.get_task("data", handle.task_id, timeout=15.0)

# Cancel directly on the handle
await handle.cancel()
await handle.cancel(timeout=15.0)

# Or cancel via network name
await network.cancel_task("data", handle.task_id)
await network.cancel_task("data", handle.task_id, timeout=15.0)

Low-level functions are also available:

from a2a_lite import get_remote_task, cancel_remote_task

status = await get_remote_task("http://localhost:8787", task_id)
await cancel_remote_task("http://localhost:8787", task_id)

Agent Card Discovery — inspect before calling

from a2a_lite import discover, AgentCardInfo

# Fetch an agent's capabilities before calling
card = await discover("http://localhost:8787")
print(f"Agent: {card.name} v{card.version}")
print(f"Skills: {[s['name'] for s in card.skills]}")
print(f"Streaming: {card.supports_streaming}")

# Auto-discover when registering
network.add("data", "http://localhost:8787", auto_discover=True)

# Validate skill exists before delegating
result = await agent.delegate("data", "fetch", query="hello", discover=True)

Client-Side SSE Streaming

When calling a remote streaming agent, consume its chunks as they arrive instead of waiting for the full response:

from a2a_lite import Agent, AgentNetwork, stream_remote_skill

# Agent consuming a remote streaming skill
@agent.skill("display")
async def display(topic: str) -> str:
    chunks = []
    async for chunk in agent.delegate("story", "tell_story", topic=topic, stream=True):
        print(chunk, end="", flush=True)
        chunks.append(chunk)
    return "".join(chunks)

Or call directly:

from a2a_lite import stream_remote_skill

async for chunk in stream_remote_skill("http://localhost:8787", "tell_story", {"topic": "dragons"}):
    print(chunk, end="", flush=True)

Per-Task Push Notifications

Register a webhook for a specific task — the server calls it when that task completes:

from a2a_lite import set_task_push_notification, delete_task_push_notification

# Get a handle when delegating
handle = await agent.delegate("data", "fetch", query="hello", return_handle=True)

# Register a webhook for this specific task
await handle.subscribe("https://my-app.com/webhook", token="secret")

# Or use the standalone function
await set_task_push_notification(
    "http://localhost:8787", handle.task_id,
    "https://my-app.com/webhook", token="secret"
)

# Retrieve registration
config = await handle.get_push_config()

# Remove registration
await handle.unsubscribe()

The server-side TaskPushRegistry is automatically created on every Agent. When a task completes, the registered webhook receives:

{"task_id": "...", "skill": "process", "result": ..., "status": "completed", "timestamp": 1234567890}

Level 9 — Lifecycle Hooks

@agent.on_startup
async def startup():
    print("Agent starting...")

@agent.on_shutdown
async def shutdown():
    print("Agent stopping...")

@agent.on_complete
async def notify(skill_name, result, ctx):
    print(f"Skill {skill_name} completed with: {result}")

@agent.on_error
async def handle_error(error: Exception):
    return {"error": str(error), "type": type(error).__name__}

Testing

AgentTestClient

Synchronous test client for pytest:

from a2a_lite import Agent, AgentTestClient

agent = Agent(name="Bot", description="Test")

@agent.skill("greet")
async def greet(name: str) -> str:
    return f"Hello, {name}!"

@agent.skill("info")
async def info(name: str, age: int) -> dict:
    return {"name": name, "age": age}


def test_simple_result():
    client = AgentTestClient(agent)
    result = client.call("greet", name="World")
    assert result == "Hello, World!"


def test_dict_result():
    client = AgentTestClient(agent)
    result = client.call("info", name="Alice", age=30)
    assert result.data["name"] == "Alice"
    assert result.data["age"] == 30


def test_list_skills():
    client = AgentTestClient(agent)
    skills = client.list_skills()
    assert "greet" in skills
    assert "info" in skills

TestResult

Every client.call() returns a TestResult:

Property Description
.data Parsed Python object (dict, list, int, str, etc.)
.text Raw text string from the response
.json() Parse text as JSON (raises on invalid JSON)
.raw_response Full A2A response dict

TestResult supports direct equality for simple values (result == 5), but use .data for subscripting (result.data["key"]).

AsyncAgentTestClient

For async test frameworks:

import pytest
from a2a_lite import AsyncAgentTestClient

@pytest.mark.asyncio
async def test_async():
    client = AsyncAgentTestClient(agent)
    result = await client.call("greet", name="World")
    assert result == "Hello, World!"
    await client.close()

Streaming Tests

def test_streaming():
    client = AgentTestClient(agent)
    results = client.stream("chat", message="hello world")
    assert len(results) == 2

CLI

Full reference: docs/cli.md.

a2a-lite init my-agent                          # Scaffold a basic project
a2a-lite create my-agent                        # Full project + tests + Docker
a2a-lite serve agent.py                         # Run an agent from a .py file
a2a-lite inspect http://localhost:8787          # Rich agent card & skills
a2a-lite info http://localhost:8787             # Compact plain-text info
a2a-lite test http://localhost:8787 greet -p name=World
a2a-lite discover http://localhost:8787 http://localhost:8788
a2a-lite doctor                                 # Local env (SDK, extras)
a2a-lite doctor http://localhost:8787           # + verify remote A2A v1.0
a2a-lite version

API Reference

Agent

Agent(
    name: str,                          # Required
    description: str,                   # Required
    version: str = "1.0.0",
    url: str = None,                    # Override auto-detected URL
    auth: AuthProvider = None,          # Authentication provider
    task_store: str | TaskStore = None, # "memory" or custom TaskStore
    cors_origins: List[str] = None,     # CORS allowed origins
    production: bool = False,           # Enable production warnings
)

Methods:

Method Description
@agent.skill(name, **config) Register a skill via decorator
@agent.middleware Register middleware via decorator
agent.add_middleware(fn) Register middleware function
@agent.on_complete Register completion hook
@agent.on_startup Register startup hook
@agent.on_shutdown Register shutdown hook
@agent.on_error Register error handler
agent.run(port=8787) Start the server
agent.get_app() Get the ASGI app (for custom deployment)

Skill Decorator

@agent.skill(
    name: str = None,             # Skill name (defaults to function name)
    description: str = None,      # Human-readable description
    tags: List[str] = None,       # Categorization tags
    streaming: bool = False,      # Enable streaming
)

Auth Providers

Provider Usage
APIKeyAuth(keys=[...]) API key auth (keys hashed with SHA-256)
BearerAuth(secret=...) JWT/Bearer token auth
OAuth2Auth(issuer=..., audience=...) OAuth2 auth (requires a2a-lite[oauth])
NoAuth() No auth (default)

Special Parameter Types

Auto-injected when detected in skill function signatures:

Type Description
TaskContext Task lifecycle management (requires task_store)
AuthResult Authentication result injection
FilePart File upload handling
DataPart Structured data handling

Orchestration

Export Description
AgentNetwork Registry of named remote agents
TaskHandle Handle to a remote task with task_id and result
AgentCardInfo Parsed agent card with name, version, skills, supports_streaming
discover(url) Fetch a remote agent's card from /.well-known/agent-card.json
get_remote_task(url, task_id) Poll the status of a remote task
cancel_remote_task(url, task_id) Request cancellation of a remote task
stream_remote_skill(url, skill, params) Stream SSE chunks from a remote agent skill

Examples

Example What it shows
01_hello_world.py Simplest agent (8 lines)
02_calculator.py Multiple skills
03_async_agent.py Async operations & lifecycle hooks
04_multi_agent/ Two agents communicating
05_with_llm.py OpenAI / Anthropic integration
06_pydantic_models.py Auto Pydantic conversion
06_task_handle_discovery.py TaskHandle + card discovery
07_middleware.py Middleware pipeline
07_client_streaming.py Client-side SSE streaming
08_streaming.py Streaming responses
09_testing.py Built-in TestClient
10_file_handling.py File upload & processing
11_task_tracking.py Progress updates
12_with_auth.py Authentication
13_mcp_tools.py MCP server integration
14_multi_agent_network.py AgentNetwork + delegate()
15_llm_openai.py OpenAI LLM skill
16_llm_anthropic.py Anthropic LLM skill
17_llm_bedrock.py AWS Bedrock LLM skill
18_per_task_push.py Per-task push notifications
19_capability_negotiation.py Capability negotiation
20_streaming_negotiation.py Streaming negotiation

A2A Protocol Mapping

Everything maps directly to the underlying protocol — no magic, no lock-in.

A2A Lite A2A Protocol
@agent.skill() Agent Skills
streaming=True SSE Streaming
TaskContext.update() Task lifecycle states
FilePart A2A File parts
DataPart A2A Data parts
Artifact A2A Artifacts
APIKeyAuth / BearerAuth Security schemes

License

MIT

Metadata

Release files for a2a-lite 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for a2a-lite 1.0.1
File Size Uploaded
a2a_lite-1.0.1.tar.gz 124.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for a2a-lite 1.0.1
File Interpreter ABI Platform
a2a_lite-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 189.5 kB

Release files / a2a_lite-1.0.1.tar.gz

Download URL a2a_lite-1.0.1.tar.gz
Size 124.6 kB
Tags Source
SHA-256 checksum
How to use checksums
c2cdfc3682ab901ce35c0058b06b864e6ddd60b9dff9f4526c3a3f36ba6478a7
BLAKE2b-256 checksum
How to use checksums
01f934ba4cbba41df49d853601924e209ef3f872bad28095479a2ab534075d8d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.

Transparency log

Release files / a2a_lite-1.0.1-py3-none-any.whl

Download URL a2a_lite-1.0.1-py3-none-any.whl
Size 64.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d0550f6cf0cbd68c7c30c10d345155f5cabfcfd1e56042afb0b5833fc7bd1093
BLAKE2b-256 checksum
How to use checksums
a3eea5219b44f19e8c4d653b20d0f786f8a211cbb95d09e3c4d725fef60f0ee5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.

Transparency log

Release history Release notifications | RSS feed

1.2.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

This release

1.0.1 This release

2 release files

0.3.9

2 release files

0.3.8

2 release files

0.3.5

2 release files

0.3.0

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page