nodus-a2a-wire
A2A 1.0.0 (Linux Foundation) wire-protocol adapter for the Nodus scripting language.
nodus-a2a-wire exposes std:tool-registered tools from a Nodus runtime as an
A2A message-only agent over HTTP+JSON/REST — agent cards, transport, and handler
dispatch.
pip install nodus-a2a-wire
Not to be confused with nodus-a2a
Two different packages, and the names are close:
| What it is | |
|---|---|
nodus-a2a-wire (this one) |
the wire protocol — A2AHttpServer, agent cards, HTTP+JSON transport |
nodus-a2a |
coordination primitives — AgentRegistry, AgentCoordinator, DeadLetterService, StuckRunWatchdog |
They share no code and neither depends on the other. Both can be installed
together: this package's Python module is nodus_a2a_wire, the other's is
nodus_a2a.
Until 0.1.0 was published this project declared the distribution name
nodus-a2a and the module name nodus_a2a, which is why it could not be
published at all — and why installing it alongside the coordinator would have
silently replaced that package's API. See
nodus-lang#477.
Requirements
No runtime dependencies, including nodus-lang. A host constructs
A2AHttpServer in Python and wires it to their own NodusRuntime.tool_registry,
so the runtime arrives by injection rather than by import.
pip install nodus-a2a-wire[flask] # optional alternative transport backend
Production authentication warning
Without a token_validator, the server runs in dev mode and accepts all requests.
Production deployments must configure a validator:
config = ServerConfig(
...
token_validator=lambda token: token == os.environ["MY_SECRET_TOKEN"],
)
Do not expose a nodus-a2a-wire server to the internet without configuring token_validator.
v0.1 scope
This is a message-only implementation (D5 decision). The server never creates A2A Tasks. All task-management operations return HTTP 501. Deferred to v0.2+:
- Task lifecycle and state machine
- SSE streaming (
SendStreamingMessage) - Push notification webhooks
- JSON-RPC and gRPC bindings
- OAuth 2.0 / OIDC / mTLS auth
See docs/design/05-deferred-features.md for the full inventory.
Quick start
from nodus.runtime.embedding import NodusRuntime
from nodus_a2a_wire import A2AHttpServer, ServerConfig
# 1. Create a NodusRuntime and register tools via tool_registry
runtime = NodusRuntime()
runtime.tool_registry.register({
"name": "myapp.greet", # must use dotted namespace: "prefix.name"
"description": "Greet someone by name",
"handler": lambda args: f"Hello, {args.get('name', 'world')}!",
})
# 2. Configure the server
config = ServerConfig(
base_url="https://myagent.example.com",
agent_name="My Greeter Agent",
agent_description="Greets people by name via A2A",
)
# 3. Build the tool list and start the server
tools = runtime.tool_registry.list_tools()
tool_names = [t["name"] for t in tools if not t.get("deprecated")]
server = A2AHttpServer(
config=config,
invoke=runtime.tool_registry.invoke,
tool_names=tool_names,
tools=tools,
)
server.serve() # blocks; use serve_in_thread() for background use
Calling the agent
Send a tool-call envelope in a DataPart:
POST /message:send HTTP/1.1
Content-Type: application/a2a+json
A2A-Version: 1.0
{
"message": {
"messageId": "msg-001",
"role": "ROLE_USER",
"parts": [{"data": {"tool": "myapp.greet", "args": {"name": "Alice"}}}]
}
}
Response:
{
"message": {
"messageId": "...",
"role": "ROLE_AGENT",
"contextId": "...",
"parts": [{"text": "Hello, Alice!", "mediaType": "text/plain"}]
}
}
Single-tool shortcut
If the agent has exactly one tool registered, any Message (even a plain TextPart)
dispatches to that tool with empty args:
{"message": {"messageId": "m1", "role": "ROLE_USER", "parts": [{"text": "hello"}]}}
Agent Card discovery
The Agent Card is served at /.well-known/agent-card.json (A2A 1.0 URI).
No authentication required.
Authentication
config = ServerConfig(
...
token_validator=lambda token: token == "my-secret-token",
)
Without a token_validator, the server runs in dev mode and accepts all requests.
Production deployments must configure a validator.
Part type dispatch
Tool return values are mapped to A2A Part variants automatically:
| Python type | A2A Part | mediaType |
|---|---|---|
str |
TextPart | text/plain |
bytes |
RawPart (base64) | application/octet-stream |
| dict / list / int / float / bool / None | DataPart | application/json |
The url Part variant is not used in automatic dispatch (reserved for v0.2+).
Error handling
Tool exceptions are returned as an error DataPart in an HTTP 200 response:
{
"message": {
"role": "ROLE_AGENT",
"parts": [{"data": {"error": "tool exploded", "type": "RuntimeError"},
"mediaType": "application/json"}]
}
}
Protocol errors (malformed JSON, invalid A2A-Version, missing auth) return HTTP 4xx with a structured error body.
Design notes
v0.1 is message-only (D5)
The server never creates or persists A2A Tasks. All task-management operations
(GetTask, ListTasks, CancelTask, streaming, push notifications) return
HTTP 501 UnsupportedOperationError. The Agent Card declares:
"capabilities": {"streaming": false, "pushNotifications": false, "extendedAgentCard": false}
v0.2 Task lifecycle: D6 inversion warning
A2A INPUT_REQUIRED / AUTH_REQUIRED are park-and-resume states — the
opposite of nodus-mcp's no-thread-parks rule. When implementing Task lifecycle
in v0.2, do not import the nodus-mcp no-park assertion. See
docs/design/05-deferred-features.md §2 for the full inversion note.
CLI
python -m nodus_a2a_wire --version
python -m nodus_a2a_wire serve --name "My Agent" --description "..." --port 8080
The serve command starts a server with no tools — useful for smoke-testing
network connectivity and verifying the Agent Card format.
Wire format
- Spec: A2A 1.0.0 /
lf.a2a.v1(proto package) - Transport: HTTP+JSON/REST only (
protocolBinding: "HTTP+JSON") - Content-Type:
application/a2a+json - Discovery:
/.well-known/agent-card.json - Version negotiation:
A2A-Versionrequest header; mismatch → HTTP 400 - Codec: proto
snake_case↔ wirecamelCase(ProtoJSON) - Signing: unsigned (v0.1); JWS signing planned for v0.2
Deferred features (v0.2+)
Task lifecycle and state machine, streaming (SendStreamingMessage),
push notification webhooks, Agent Card signing (JWS/RFC 7515), extended
authenticated card, JSON-RPC binding, gRPC binding, OAuth2/OIDC/mTLS,
tenant routing, 0.3 wire-dialect compatibility.
See docs/design/05-deferred-features.md for the full inventory.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nodus_a2a_wire-0.1.0.tar.gz.
File metadata
- Download URL: nodus_a2a_wire-0.1.0.tar.gz
- Upload date:
- Size: 56.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7376ff033b42f66ad9189f1b7105c3c834f955e2e9db0f1dc9002744ea8f5701
|
|
| MD5 |
e53d0434ddf05b6c37ab9ba63534a974
|
|
| BLAKE2b-256 |
1672a07104b9eb0acb0232c4bd8ae8f9d700f71d00dc48624ead0a3f36e2db0d
|
File details
Details for the file nodus_a2a_wire-0.1.0-py3-none-any.whl.
File metadata
- Download URL: nodus_a2a_wire-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7d711d745b66c890ef539065264b1bba8e315d2c8ca2e0902ed9f33ccee1960
|
|
| MD5 |
d1dc25b47836364167ca93d918499306
|
|
| BLAKE2b-256 |
ccc251387281cce10fb6ed3092924be3718c7e6a8a281fc32640bd2821f9be87
|