A2A¶
tulip.a2a implements the Agent-to-Agent protocol: one agent calls another
over HTTP, across process and organisation boundaries, without either side
importing the other's code.
Two generations of the wire format ship side by side. The A2AV1* types are
the v1 spec — use these for anything new. The unprefixed types are the earlier
shape, kept because deployed peers still speak it; A2AServer accepts both.
For the concepts, start with the A2A protocol and the walkthrough notebook.
Client and server¶
A2AServer exposes an agent over the protocol; A2AClient calls one. Neither
requires the other side to be built with Tulip.
A2AServer ¶
A2AServer(agent: Any, name: str = 'Tulip Agent', description: str = '', skills: list[AgentSkill] | list[str] | None = None, url: str = '', provider: AgentProvider | None = None, version: str = '0.1.0', api_key: str | None = None, allow_unauthenticated: bool = False)
Bases: A2AV1ServerMixin
Expose a Tulip Agent as a spec-compliant A2A endpoint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Any
|
A Tulip |
required |
name
|
str
|
Display name for the Agent Card. |
'Tulip Agent'
|
description
|
str
|
One-line description for the Agent Card. |
''
|
skills
|
list[AgentSkill] | list[str] | None
|
List of :class: |
None
|
url
|
str
|
Public URL the agent is reachable at — set this for
cross-process / cross-host deployments so the card's |
''
|
provider
|
AgentProvider | None
|
Optional :class: |
None
|
version
|
str
|
Agent semver — useful for capability negotiation. |
'0.1.0'
|
api_key
|
str | None
|
Bearer token required on every route; if |
None
|
allow_unauthenticated
|
bool
|
Bind to non-loopback without a key. Use only behind an upstream proxy that terminates auth. |
False
|
Example::
from tulip import Agent
from tulip.a2a import A2AServer
from tulip.a2a.spec import AgentSkill
server = A2AServer(
agent=my_agent,
name="Research Agent",
description="Open-web research with citations.",
skills=[
AgentSkill(
id="research",
name="Research",
description="Answer with cited sources.",
tags=["search", "summarise"],
),
],
url="https://research.example.com",
api_key="secret",
)
server.run(port=8001)
Source code in .sdk/src/tulip/a2a/protocol.py
run ¶
Run the A2A server.
Defaults to loopback binding. Non-loopback bindings require
either api_key to be set or allow_unauthenticated=True.
Source code in .sdk/src/tulip/a2a/protocol.py
A2AClient ¶
A2AClient(url: str, api_key: str | None = None, timeout: float | Timeout | None = None, protocol_version: str | None = A2A_V1_PROTOCOL_VERSION)
Call a remote A2A agent from Tulip.
Spec-compliant methods:
- :meth:
get_agent_card— fetches/.well-known/agent-card.json, falling back to the legacy/agent-cardendpoint. - :meth:
send_message— JSON-RPCmessage/send; returns a :class:Taskyou can poll with :meth:get_task. - :meth:
send_message_streaming— JSON-RPCmessage/stream; yields events from the SSE stream. - :meth:
get_task, :meth:cancel_task— task lifecycle.
Plus the legacy convenience APIs preserved from the pre-spec implementation:
- :meth:
invoke— flat string-in / string-out over/a2a/invoke. - :meth:
as_tool— wrap a remote agent as a Tulip@tool.
Source code in .sdk/src/tulip/a2a/protocol.py
get_agent_card
async
¶
Fetch the remote agent's capability card.
Tries the spec well-known URL first, falls back to the legacy
/agent-card endpoint for older peers.
Source code in .sdk/src/tulip/a2a/protocol.py
send_message
async
¶
Send a message via JSON-RPC message/send and return the Task.
Source code in .sdk/src/tulip/a2a/protocol.py
send_message_streaming
async
¶
send_message_streaming(message: Message, *, timeout: float | Timeout | None = None) -> AsyncIterator[dict[str, Any]]
Send a message via JSON-RPC streaming and yield events.
Source code in .sdk/src/tulip/a2a/protocol.py
get_task
async
¶
Fetch a task by id (JSON-RPC tasks/get).
Source code in .sdk/src/tulip/a2a/protocol.py
list_tasks
async
¶
list_tasks(*, context_id: str | None = None, status: TaskState | str | None = None, page_size: int | None = None, page_token: str | None = None, history_length: int | None = None, include_artifacts: bool | None = None) -> tuple[list[Task], str]
List known tasks.
Returns (tasks, next_page_token). The client maps v1.0 wire
enum states back into SDK-shaped :class:Task objects.
Source code in .sdk/src/tulip/a2a/protocol.py
cancel_task
async
¶
Cancel a task (JSON-RPC tasks/cancel).
Source code in .sdk/src/tulip/a2a/protocol.py
invoke
async
¶
Send a flat text prompt over the legacy /a2a/invoke.
Useful when you control both ends of the wire and want a one-line
round-trip; spec-compliant peers should prefer
:meth:send_message so they can read the full :class:Task.
Source code in .sdk/src/tulip/a2a/protocol.py
as_tool ¶
Wrap this remote agent as a Tulip @tool.
Source code in .sdk/src/tulip/a2a/protocol.py
Agent cards — discovery¶
An agent card is what a peer publishes about itself: who runs it, what it can do, and how to reach it. It is the only thing a caller needs before the first request.
AgentCard ¶
Bases: BaseModel
Public Agent Card (spec §5.5).
Published at /.well-known/agent-card.json. The legacy
/agent-card endpoint serves the same payload for backwards
compatibility with peers that haven't picked up the well-known URL.
AgentSkill ¶
Bases: BaseModel
A discrete capability the agent advertises in its card.
AgentCapabilities ¶
Bases: BaseModel
Optional protocol-level capabilities the agent supports.
Per spec, these are declarations: a peer queries the agent card to know whether to attempt streaming or push-notification flows.
AgentProvider ¶
Bases: BaseModel
The organization / publisher behind the agent.
AgentInterface ¶
Bases: BaseModel
A concrete protocol binding exposed by an A2A v1.0 agent.
v1 protocol¶
The current wire format. A2A_V1_PROTOCOL_VERSION is the version string sent
on the wire and the one a peer negotiates against.
Sending a message¶
A2AV1SendMessageRequest ¶
Bases: BaseModel
A2A v1.0 SendMessage request params.
A2AV1SendMessageResponse ¶
Bases: BaseModel
A2A v1.0 SendMessageResponse oneof.
A2AV1SendMessageConfiguration ¶
Bases: BaseModel
A2A v1.0 SendMessage configuration.
A2AV1Message ¶
Bases: BaseModel
A2A v1.0 Message.
A2AV1Part ¶
Bases: BaseModel
A2A v1.0 Part oneof.
Exactly one of text, data, raw or url should be set by
callers. The model is intentionally permissive enough to round-trip
extension fields that are outside Tulip's plain-text default.
A2AV1Role ¶
Bases: StrEnum
A2A v1.0 message role enum names.
Tasks¶
A request that is not answered immediately becomes a task the caller polls or
streams. A2AV1TaskState is the state machine; the update events are what a
streaming caller receives.
A2AV1Task ¶
Bases: BaseModel
A2A v1.0 Task.
A2AV1TaskState ¶
Bases: StrEnum
A2A v1.0 task state enum names.
A2AV1TaskStatus ¶
Bases: BaseModel
A2A v1.0 TaskStatus.
A2AV1GetTaskRequest ¶
Bases: BaseModel
A2A v1.0 GetTask request params.
A2AV1CancelTaskRequest ¶
Bases: BaseModel
A2A v1.0 CancelTask request params.
A2AV1ListTasksRequest ¶
Bases: BaseModel
A2A v1.0 ListTasks request params.
A2AV1ListTasksResponse ¶
Bases: BaseModel
A2A v1.0 ListTasks response.
Streaming and artifacts¶
A2AV1StreamResponse ¶
Bases: BaseModel
A2A v1.0 StreamResponse oneof.
A2AV1TaskStatusUpdateEvent ¶
Bases: BaseModel
A2A v1.0 TaskStatusUpdateEvent.
v1.0 stream completion is inferred from task state and stream closure;
the pre-v1 final field is intentionally absent.
A2AV1TaskArtifactUpdateEvent ¶
Bases: BaseModel
A2A v1.0 TaskArtifactUpdateEvent.
A2AV1Artifact ¶
Bases: BaseModel
A2A v1.0 Artifact.
Messages and parts¶
A message is a list of parts. Part is the discriminated union — a part is
text, a file, or structured data, and the kind field decides which.
Message ¶
Bases: BaseModel
A user/agent message with one or more typed parts (spec §6.4).
Part
module-attribute
¶
TextPart ¶
Bases: BaseModel
A plain-text message part.
DataPart ¶
Bases: BaseModel
A structured-data (e.g. JSON) message part.
FilePart ¶
Bases: BaseModel
A file message part — either inline bytes or a URI reference.
FileWithBytes ¶
Bases: BaseModel
A file referenced by inline base64 bytes.
FileWithUri ¶
Bases: BaseModel
A file referenced by URI.
Artifact ¶
Bases: BaseModel
A typed result attached to a Task (spec §6.7).
Tasks¶
Task ¶
Bases: BaseModel
A unit of work tracked through the lifecycle (spec §6.1).
TaskState ¶
Bases: StrEnum
Task lifecycle states (spec §6.3).
TaskStatus ¶
Bases: BaseModel
Status block on a Task (spec §6.2).
TaskIdParams ¶
Bases: BaseModel
Identifier-only params (cancel, push-config get/list/delete).
TaskQueryParams ¶
Bases: BaseModel
Parameters for tasks/get (spec §7.3).
TaskStatusUpdateEvent ¶
Bases: BaseModel
Status transition event in the SSE stream (spec §7.2.2).
TaskArtifactUpdateEvent ¶
Bases: BaseModel
Artifact-attach event in the SSE stream (spec §7.2.3).
MessageSendParams ¶
Bases: BaseModel
Parameters for message/send and message/stream (spec §7.1).
MessageSendConfiguration ¶
Bases: BaseModel
Optional per-call configuration (spec §7.1.4).
Push notifications¶
For work long enough that polling is the wrong shape: the peer calls you back when the task changes.
PushNotificationConfig ¶
Bases: BaseModel
Webhook config attached to a Task for async updates.
TaskPushNotificationConfig ¶
Bases: BaseModel
Bundle: which task + the webhook config (spec §7.5).
PushNotificationAuthenticationInfo ¶
Bases: BaseModel
Auth shape for push-notification webhook delivery.
JSON-RPC envelope¶
The transport shapes. You rarely construct these directly — A2AClient and
A2AServer do — but an error response is worth being able to read.
JsonRpcRequest ¶
Bases: BaseModel
A JSON-RPC 2.0 request envelope.
The id MAY be omitted for notifications, but A2A's request methods always require a response so callers should always send one.
JsonRpcSuccessResponse ¶
Bases: BaseModel
JSON-RPC 2.0 successful response.
JsonRpcErrorResponse ¶
Bases: BaseModel
JSON-RPC 2.0 error response.
JsonRpcError ¶
Bases: BaseModel
JSON-RPC 2.0 error object.
Legacy shapes¶
The pre-v1 request/response types. A2AServer still accepts them so deployed
peers keep working; do not build anything new on them.
A2AMessage ¶
Bases: BaseModel
Legacy flat message — preserved so peers + tests that still call
/a2a/invoke keep working. Spec-aware peers should use
:class:tulip.a2a.spec.Message.
A2ARequest ¶
Bases: BaseModel
Legacy request envelope for POST /a2a/invoke.
A2AResponse ¶
Bases: BaseModel
Legacy response envelope from POST /a2a/invoke.