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.
Metadata
Release files for nodus-a2a-wire 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nodus_a2a_wire-0.1.0.tar.gz | 56.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nodus_a2a_wire-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 75.9 kB
Release files / nodus_a2a_wire-0.1.0.tar.gz
| Download URL | nodus_a2a_wire-0.1.0.tar.gz |
|---|---|
| Size | 56.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7376ff033b42f66ad9189f1b7105c3c834f955e2e9db0f1dc9002744ea8f5701
|
|
BLAKE2b-256 checksum How to use checksums |
1672a07104b9eb0acb0232c4bd8ae8f9d700f71d00dc48624ead0a3f36e2db0d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / nodus_a2a_wire-0.1.0-py3-none-any.whl
| Download URL | nodus_a2a_wire-0.1.0-py3-none-any.whl |
|---|---|
| Size | 19.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b7d711d745b66c890ef539065264b1bba8e315d2c8ca2e0902ed9f33ccee1960
|
|
BLAKE2b-256 checksum How to use checksums |
ccc251387281cce10fb6ed3092924be3718c7e6a8a281fc32640bd2821f9be87
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|