Skip to content

Python SDK Internals

This page documents the internal layout of the zyndai-agent Python package. If you only want to install it, scaffold an agent, and run it, see Reference → Python SDK API.

Use this page when:

  • You're contributing to the SDK.
  • You're embedding parts of it in another framework — e.g. calling dns_registry directly without a full agent.
  • You're debugging a stack trace and want to know which module owns the failure.

Module map

zyndai_agent/
├── __init__.py                  # Public API re-exports
├── base.py                      # ZyndBase — shared identity, heartbeat, registration
├── agent.py                     # ZyndAIAgent — wraps an LLM framework
├── service.py                   # ZyndService — wraps a plain Python function

├── identity.py                  # IdentityManager mixin (legacy DID layer)
├── ed25519_identity.py          # Canonical identity — keypair gen, sign, verify, HD derivation

├── entity_card.py               # EntityCard model + signing helpers
├── entity_card_loader.py        # Reads / writes .well-known/agent-card.json

├── dns_registry.py              # AgentDNS HTTP client — register, search, resolve, heartbeat
├── search.py                    # Search wrapper with filters + result models

├── communication.py             # Webhook HTTP client (outbound)
├── webhook_communication.py     # Flask server (inbound)
├── message.py                   # AgentMessage envelope model
├── payload.py                   # Payload schemas + validation

├── payment.py                   # x402 client + server middleware

├── a2a/                         # A2A server — handlers, tasks, payload routing
│   ├── server.py                # HandlerInput, TaskHandle, install_handler
│   └── ...

├── config_manager.py            # Reads / writes ~/.zynd/* configs
└── utils.py                     # Misc helpers — slug, time, ports

Core lifecycle

base.ZyndBase

The shared lifecycle. Both ZyndAIAgent and ZyndService extend it.

On __init__:

  1. Resolve the keypair (config.keypair_path or env or HD-derive).
  2. Compute entity_id = "zns:" + sha256(pubkey)[:16].hex().
  3. Build the X402PaymentProcessor and capture pay_to_address.
  4. Set up Flask routes (or, in TS, Express).
  5. Install the default handler (which the subclass overrides).

On start():

  1. Spin up the HTTP server.
  2. Build, sign, and write .well-known/agent-card.json.
  3. POST /v1/entities with the developer proof.
  4. Open the WSS heartbeat (silently degrades if websockets isn't installed; the SDK logs a warning).
  5. Block on the HTTP server.

agent.ZyndAIAgent

Adds five framework setters and one custom-handler entry point.

MethodWhat it does
set_langchain_agent(executor)Stores executor + sets agent_framework = LANGCHAIN
set_langgraph_agent(graph)Same for LangGraph
set_crewai_agent(crew)Same for CrewAI
set_pydantic_ai_agent(agent)Same for PydanticAI
set_custom_agent(fn)Stores Callable[[str], str] (sync or async)
on_message(handler)Replaces the default A2A dispatcher with your own
invoke(text, **kwargs)Universal in-process dispatcher used by the default handler

The default handler (_default_handler) extracts input.message.content, calls invoke, and returns task.complete({"text": result}).

service.ZyndService

MethodWhat it does
set_handler(fn)(text: str) -> str (sync or async)
on_message(handler)Full A2A access
invoke(text)Manual in-process dispatch

Identity

ed25519_identity

Canonical identity layer.

FunctionPurpose
generate_keypair()Fresh Ed25519 keypair
load_keypair(path)Returns (Ed25519Keypair, derivation_metadata?)
save_keypair(kp, path, derivation_metadata=None)Writes JSON
sign(private_key, message)Detached Ed25519 signature, base64
verify(public_key_b64, message, signature)bool
derive_agent_keypair(dev_priv_key, index)HD derive
create_derivation_proof(dev_kp, agent_pub, index){developer_public_key, agent_index, developer_signature}
generate_agent_id(public_key_bytes)"zns:" + sha256(pubkey)[:16].hex()

The HD formula is:

seed_bytes = SHA-512(dev_private_key_bytes || "zns:agent:" || uint32_be(index))[:32]
agent_keypair = Ed25519(seed_bytes)

identity.IdentityManager (legacy)

Older mixin still imported by some examples. New code uses ed25519_identity directly.

Entity card

entity_card

Pydantic model for the live Agent Card published at /.well-known/agent-card.json. Fields: entity_id, name, capabilities, endpoints, pricing, input_schema, output_schema, signed_at, signature.

entity_card_loader

Builds, signs, and writes the card on every start(). Re-runs on each start so updates propagate without manual intervention.

Registry client

dns_registry

HTTP client to AgentDNS. One method per endpoint family:

MethodEndpoint
register_entity(...)POST /v1/entities (signs internally)
update_entity(...)PUT /v1/entities/{id}
deregister_entity(...)DELETE /v1/entities/{id}
get_entity(...)GET /v1/entities/{id}
search_entities(...)POST /v1/search
resolve_fqan(...)GET /v1/resolve/{handle}/{name}
claim_handle(...)POST /v1/handles
bind_name(...)POST /v1/names
Heartbeat WS loopConnect to WSS /v1/entities/{id}/ws, ping every 30 s

Pythonic wrapper on top of dns_registry.search — typed filter args, result paging, optional Agent Card enrichment.

Communication

webhook_communication

The Flask server with three routes:

RouteMethodBehaviour
/webhookPOSTAsync; returns 202
/webhook/syncPOSTSync; blocks for the handler's response
/.well-known/agent-card.jsonGETServes the live card

x402 middleware mounts on /webhook/sync if entity_pricing is set.

communication

Outbound HTTP client. Wraps requests plus the x402 client middleware.

message.AgentMessage

{ message_id, sender_id, sender_public_key, receiver_id,
  content, message_type, conversation_id, in_reply_to,
  timestamp, signature }

Signed by the sender, verified by the receiver against the registry's published public key.

payload

Pydantic base classes for request/response payloads. The scaffolded payload.py exports RequestPayload and ResponsePayload that the SDK validates against.

Payment

payment.X402PaymentProcessor

Two halves rolled into one class:

  • Server middleware — when mounted on a Flask route, it returns 402 with a Base / Sepolia USDC quote when the entity has pricing set.
  • Client — wraps requests.Session; intercepts 402 responses, signs a USDC transfer with the eth-account lib, retries with the X-Payment header.

proc.account.address is the EVM wallet derived from the Ed25519 seed.

A2A server (zyndai_agent.a2a)

The newer A2A protocol surface. Owns:

  • HandlerInput — verified inbound message + parsed payload.
  • TaskHandlecomplete, fail, update_progress, request_clarification.
  • install_handler(fn) — low-level handler installer used by on_message and the framework setters.

Config

config_manager

Reads / writes ~/.zynd/developer.json, ~/.zynd/agents/<name>/keypair.json, and the rest of the on-disk state. Single source of truth for paths so the SDK and the CLI agree on layout.

Lifecycle thread model

ThreadWhat it does
MainRuns the Flask server (Werkzeug or gunicorn)
HeartbeatOne background thread for the WSS heartbeat; auto-reconnects with exp backoff
Default handlerRuns in the request thread; sync framework calls block, async ones are awaited via asyncio.run
task.update_progress callbacksFire on the request thread; future versions may move to a worker pool

Error surfaces

The SDK raises typed exceptions at boundaries:

ExceptionWhere
IdentityErrorBad keypair file, bad signature, bad derivation proof
RegistryErrorHTTP error from /v1/... (with code, field)
WebhookErrorInbound message failed validation or signature check
PaymentErrorx402 settlement failed

These all subclass ZyndError for catch-all paths.

Versioning

zyndai-agent follows semver. The current line is 0.6.x. Breaking changes between minor versions are listed in the CHANGELOG.md at the top of the repo.

See also

Released under the MIT License.