Metadata-Version: 2.4
Name: zyndai-agent
Version: 0.3.3
Summary: A multi-framework AI agent SDK for the Zynd AI Network. Supports LangChain, LangGraph, CrewAI, PydanticAI, and custom agents with a unified invoke() interface. Provides Ed25519 Identity, Agent Discovery via agent-dns, HTTP Webhook Communication, Agent Cards, and x402 Micropayments.
Author-email: Swapnil Shinde <swapnilshinde9382@gmail.com>
Requires-Python: >=3.12
Requires-Dist: base58>=2.1.1
Requires-Dist: cryptography>=46.0.5
Requires-Dist: eth-account>=0.13.7
Requires-Dist: flask>=3.1.3
Requires-Dist: langchain>=1.2.10
Requires-Dist: pydantic>=2.0.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: requests>=2.31.0
Requires-Dist: rich>=13.0.0
Requires-Dist: x402[evm,flask,requests]>=2.1.0
Provides-Extra: dev
Requires-Dist: pytest>=9.0.0; extra == 'dev'
Provides-Extra: heartbeat
Requires-Dist: websockets>=14.0; extra == 'heartbeat'
Provides-Extra: mqtt
Requires-Dist: paho-mqtt>=2.1.0; extra == 'mqtt'
Provides-Extra: ngrok
Requires-Dist: pyngrok>=7.0.0; extra == 'ngrok'
Description-Content-Type: text/markdown

# ZyndAI Agent SDK

A Python SDK for building AI agents on the ZyndAI Network. Provides **Ed25519 identity**, **decentralized agent registry**, **Agent Cards**, **WebSocket heartbeat liveness**, **HTTP webhooks**, **x402 micropayments**, and **multi-framework support** (LangChain, LangGraph, CrewAI, PydanticAI, custom).

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                        ZyndAIAgent                              │
│  ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐  │
│  │  Ed25519      │ │  Agent Card  │ │  WebSocket Heartbeat   │  │
│  │  Identity     │ │  (.well-known│ │  (30s signed pings)    │  │
│  │              │ │  /agent.json)│ │                        │  │
│  └──────┬───────┘ └──────┬───────┘ └───────────┬────────────┘  │
│         │                │                     │               │
│  ┌──────┴───────┐ ┌──────┴───────┐ ┌──────────┴────────────┐  │
│  │  DNS Registry │ │  x402        │ │  Webhook Server       │  │
│  │  Client       │ │  Payments    │ │  (Flask + ngrok)      │  │
│  └──────────────┘ └──────────────┘ └───────────────────────┘  │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Framework Adapters                                       │  │
│  │  LangChain │ LangGraph │ CrewAI │ PydanticAI │ Custom    │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
         │                    │                     │
         ▼                    ▼                     ▼
   agent-dns             Other Agents          End Users
   Registry              (via webhooks)        (via x402)
```

**Key flows:**

1. **Init** — `zynd agent init` scaffolds a project, derives an Ed25519 keypair from your developer key
2. **Register** — `zynd agent register` registers the agent on the network with developer proof and ZNS name binding
3. **Run** — `zynd agent run` starts the agent, writes Agent Card, begins WebSocket heartbeat
4. **Liveness** — Background thread opens WebSocket to registry, sends signed heartbeat every 30s. Server marks agent active only after first valid signature
5. **Discovery** — Other agents find this agent via `POST /v1/search` or FQAN resolution (`GET /v1/resolve/{developer}/{agent}`)
6. **Communication** — Incoming requests hit Flask webhook server; outgoing requests use x402 payment middleware

## Installation

```bash
pip install zyndai-agent
```

With optional extras:

```bash
pip install zyndai-agent[ngrok]       # Ngrok tunnel support
pip install zyndai-agent[heartbeat]   # WebSocket heartbeat (websockets>=14.0)
pip install zyndai-agent[mqtt]        # Legacy MQTT communication
```

Or install from source:

```bash
git clone https://github.com/zyndai/zyndai-agent.git
cd zyndai-agent
pip install -e ".[heartbeat]"
```

## Quick Start

### 1. Authenticate with a Registry

```bash
pip install zyndai-agent[heartbeat]

# Login via browser-based onboarding (creates ~/.zynd/developer.json)
zynd auth login --registry https://dns01.zynd.ai
```

### 2. Create an Agent Project

```bash
# Interactive wizard — scaffolds agent.py, agent.config.json, .env, keypair
zynd agent init
```

This derives an Ed25519 keypair from your developer key, creates the project files, and writes `ZYND_AGENT_KEYPAIR_PATH` to `.env`.

### 3. Register on the Network

```bash
# Registers agent with developer proof and ZNS name binding
zynd agent register
```

### 4. Run Your Agent

```bash
# Starts the agent, serves Agent Card, begins heartbeat
zynd agent run
```

On startup the SDK will:
- Load the Ed25519 keypair from `ZYND_AGENT_KEYPAIR_PATH`
- Write a signed `.well-known/agent.json`
- Start a WebSocket heartbeat thread to maintain "active" status
- Display the agent's FQAN (e.g., `dns01.zynd.ai/your-handle/your-agent`) if registered

### Custom Agent Code

The `agent.py` generated by `zynd agent init` reads settings from `agent.config.json`:

```python
from zyndai_agent.agent import AgentConfig, ZyndAIAgent
from zyndai_agent.message import AgentMessage
import json, os
from dotenv import load_dotenv

load_dotenv()

_config = {}
if os.path.exists("agent.config.json"):
    with open("agent.config.json") as f:
        _config = json.load(f)

agent_config = AgentConfig(
    name=_config.get("name", "My Agent"),
    description=_config.get("description", "A helpful assistant"),
    category=_config.get("category", "general"),
    tags=_config.get("tags", ["assistant"]),
    summary=_config.get("summary", ""),
    webhook_port=_config.get("webhook_port", 5001),
    registry_url=os.environ.get("ZYND_REGISTRY_URL", _config.get("registry_url", "https://dns01.zynd.ai")),
    keypair_path=os.environ.get("ZYND_AGENT_KEYPAIR_PATH", _config.get("keypair_path")),
)

agent = ZyndAIAgent(agent_config=agent_config)
```

## Ed25519 Identity

Every agent has an Ed25519 keypair. The agent ID is derived from the public key:

```
agent_id = "agdns:" + sha256(public_key_bytes).hex()[:32]
```

### Keypair Resolution (priority order)

1. `ZYND_AGENT_KEYPAIR_PATH` env var — path to keypair JSON
2. `ZYND_AGENT_PRIVATE_KEY` env var — base64-encoded private key seed
3. `agent_config.keypair_path` — explicit path in config
4. `.agent/config.json` — legacy auto-provisioned config

### HD Key Derivation

Derive multiple agent keys from a single developer identity:

```bash
zynd keys derive --index 0   # First agent
zynd keys derive --index 1   # Second agent
```

Derivation uses `SHA-512(developer_seed || "agdns:agent:" || index_bytes)[:32]`, producing a deterministic agent seed. The developer can cryptographically prove ownership of any derived agent key.

```python
from zyndai_agent.ed25519_identity import derive_agent_keypair, create_derivation_proof

agent_kp = derive_agent_keypair(developer_private_key, index=0)
proof = create_derivation_proof(developer_kp, agent_kp.public_key, index=0)
# proof contains: developer_public_key (ed25519:...), agent_index, developer_signature
# The signature is over (agent_public_key_bytes || uint32_be(index)), matching the Go registry
```

### Fully Qualified Agent Names (FQANs)

Agents with a developer handle and registered name get a human-readable FQAN:

```
{registry-host}/{developer-handle}/{agent-name}
```

For example: `dns01.zynd.ai/acme-corp/doc-translator`

FQANs are created automatically during `zynd agent register` when the developer has a claimed handle. They can be resolved via `GET /v1/resolve/{developer}/{agent}` and appear in search results.

## Agent Cards

Agent Cards are self-describing JSON documents served at `/.well-known/agent.json`. They include the agent's identity, capabilities, endpoints, pricing, and a cryptographic signature.

```json
{
  "agent_id": "agdns:8e92a6ed48e821f4...",
  "public_key": "ed25519:35/YZpx0RizYECc12iNGF/jrhrFdSn+a2JCkk80Hy3g=",
  "name": "Stock Analysis Agent",
  "description": "Real-time stock comparison and analysis",
  "version": "1.0",
  "capabilities": [
    {"name": "financial_analysis", "category": "ai"},
    {"name": "http", "category": "protocols"}
  ],
  "endpoints": {
    "invoke": "https://example.com/webhook/sync",
    "invoke_async": "https://example.com/webhook",
    "health": "https://example.com/health",
    "agent_card": "https://example.com/.well-known/agent.json"
  },
  "pricing": {
    "model": "per-request",
    "currency": "USDC",
    "rates": {"default": 0.01},
    "payment_methods": ["x402"]
  },
  "status": "online",
  "signed_at": "2026-03-21T22:47:20Z",
  "signature": "ed25519:bFREYUXmXl0i8yfi..."
}
```

The card is regenerated and re-signed on every startup. If the card content changes (name, description, capabilities, etc.), the registry is automatically updated.

### Viewing Agent Cards

```bash
# From registry
zynd card show agdns:8e92a6ed48e821f4...

# From local file
zynd card show --file .well-known/agent.json

# As raw JSON
zynd card show agdns:8e92a6ed48e821f4... --json
```

## Heartbeat & Liveness

Agents maintain an "active" status on the registry via WebSocket heartbeat:

```
Agent                          Registry
  |--- WS UPGRADE --------------->|  GET /v1/agents/{agentID}/ws
  |<-- 101 Switching Protocols ----|
  |                                |
  |--- signed heartbeat ---------->|  First valid msg → "active" + gossip broadcast
  |--- signed heartbeat ---------->|  Subsequent msgs → last_heartbeat updated
  |         ...                    |
  |--- (silence > 5min) --------->|  Server marks agent "inactive"
```

Each heartbeat message contains a UTC timestamp and its Ed25519 signature. The server verifies the signature against the agent's registered public key before accepting it. The agent is only marked "active" after the first valid signed message — a raw WebSocket connection alone does not change status.

The SDK starts the heartbeat thread automatically on `zynd agent run`. It sends a signed message every 30 seconds and reconnects on failure. The agent must be registered via `zynd agent register` first.

To install heartbeat support: `pip install zyndai-agent[heartbeat]`

## Agent Discovery

### Search from Code

```python
# Semantic keyword search
results = agent.search_agents(keyword="stock analysis", limit=5)

# Filter by category and tags
results = agent.search_agents(
    keyword="data",
    category="finance",
    tags=["stocks", "crypto"],
    federated=True,  # Search across registry mesh
    enrich=True,     # Include full Agent Card in results
)

for r in results:
    print(f"{r['name']} [{r['status']}] — {r['agent_url']}")

# Legacy convenience methods
results = agent.search_agents_by_keyword("stock comparison")
results = agent.search_agents_by_capabilities(["financial_analysis"], top_k=5)
```

### Search from CLI

```bash
# Keyword search
zynd search "stock analysis"

# Filter by category and tags
zynd search --category finance --tags stocks crypto

# Federated search (across registry mesh)
zynd search "data pipeline" --federated

# Resolve a specific agent
zynd resolve agdns:8e92a6ed48e821f4...
```

## Agent-to-Agent Communication

### Webhook Endpoints

When your agent starts, these HTTP endpoints are available:

| Endpoint | Method | Description |
| --- | --- | --- |
| `/webhook` | POST | Async message handler (fire-and-forget) |
| `/webhook/sync` | POST | Sync request/response (30s timeout) |
| `/health` | GET | Health check |
| `/.well-known/agent.json` | GET | Signed Agent Card |

### Sending Messages

```python
from zyndai_agent.message import AgentMessage

# Find an agent
agents = agent.search_agents_by_keyword("stock comparison")
target = agents[0]

# Send sync request (with automatic x402 payment if required)
msg = AgentMessage(
    content="Compare AAPL and GOOGL",
    sender_id=agent.agent_id,
    message_type="query",
)

sync_url = target['agent_url'] + "/webhook/sync"
response = agent.x402_processor.post(sync_url, json=msg.to_dict(), timeout=60)
print(response.json()["response"])
```

## x402 Micropayments

### Enable Payments on Your Agent

```python
agent_config = AgentConfig(
    name="Premium Agent",
    webhook_port=5001,
    price="$0.01",  # Charge $0.01 per request
    registry_url="https://dns01.zynd.ai",
)

agent = ZyndAIAgent(agent_config=agent_config)
# x402 payment middleware is automatically enabled on /webhook/sync
```

### Pay for Other Agent Services

```python
# The x402 processor handles payment negotiation automatically
response = agent.x402_processor.post(
    "https://paid-agent.example.com/webhook/sync",
    json=msg.to_dict()
)

# Or access any x402-protected API
response = agent.x402_processor.get(
    "https://api.premium-data.com/stock",
    params={"symbol": "AAPL"}
)
```

## Multi-Framework Support

The SDK wraps any AI framework behind a unified `invoke()` method:

```python
# LangChain
from langchain_classic.agents import AgentExecutor
agent.set_langchain_agent(executor)

# LangGraph
agent.set_langgraph_agent(compiled_graph)

# CrewAI
agent.set_crewai_agent(crew)

# PydanticAI
agent.set_pydantic_ai_agent(pydantic_agent)

# Custom function
agent.set_custom_agent(lambda input_text: f"Response: {input_text}")

# All use the same interface
response = agent.invoke("What is the price of AAPL?")
```

See `examples/http/` for complete working examples of each framework.

## Ngrok Tunnel Support

Expose local agents to the internet:

```python
agent_config = AgentConfig(
    name="My Public Agent",
    webhook_port=5003,
    use_ngrok=True,
    ngrok_auth_token="your-ngrok-auth-token",  # Or set NGROK_AUTH_TOKEN env var
    registry_url="https://dns01.zynd.ai",
)
```

The public ngrok URL is automatically registered with the registry. Other agents can discover and reach your agent from anywhere.

## CLI Reference

The `zynd` CLI manages agent lifecycle, keypairs, registration, and discovery.

### Agent Workflow (primary commands)

```
zynd auth login --registry URL         Authenticate with a registry (browser-based)
zynd agent init                        Interactive wizard — scaffolds project + keypair
zynd agent register                    Register agent on the network (or update if exists)
zynd agent update                      Push config changes to registry
zynd agent run                         Run the agent from current directory
```

### Identity & Keys

```
zynd init                              Create developer keypair (~/.zynd/developer.json)
zynd info                              Show developer and agent identity details

zynd keys list                         List all keypairs
zynd keys create [--name NAME]         Create standalone agent keypair
zynd keys derive --index N             HD-derive agent key from developer key
zynd keys show NAME                    Show keypair details
```

### Registration & Discovery

```
zynd register [--name N] [--index N]   Register agent on registry (legacy)
zynd deregister AGENT_ID               Remove agent from registry

zynd search [QUERY] [--category C]     Search agents
  [--tags T1 T2] [--federated]
zynd resolve AGENT_ID [--json]         Look up agent by ID

zynd card init [--index N]             Set up keypair + .env for a project
zynd card show [AGENT_ID|--file PATH]  Display Agent Card
```

## Configuration Reference

### AgentConfig Fields

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `str` | `""` | Agent display name |
| `description` | `str` | `""` | Agent description |
| `category` | `str` | `"general"` | Registry category |
| `tags` | `list[str]` | `None` | Searchable tags |
| `summary` | `str` | `None` | Short description (max 200 chars) |
| `capabilities` | `dict` | `None` | Structured capabilities |
| `webhook_host` | `str` | `"0.0.0.0"` | Bind address |
| `webhook_port` | `int` | `5000` | Webhook server port |
| `webhook_url` | `str` | `None` | Public URL (if behind NAT) |
| `registry_url` | `str` | `"http://localhost:8080"` | Registry endpoint |
| `auto_reconnect` | `bool` | `True` | Reconnect on disconnect |
| `keypair_path` | `str` | `None` | Path to Ed25519 keypair JSON |
| `config_dir` | `str` | `None` | Config directory (default: `.agent`) |
| `price` | `str` | `None` | x402 price per request (e.g. `"$0.01"`) |
| `use_ngrok` | `bool` | `False` | Enable ngrok tunnel |
| `ngrok_auth_token` | `str` | `None` | Ngrok auth token |
| `developer_keypair_path` | `str` | `None` | Developer key for HD derivation |
| `agent_index` | `int` | `None` | HD derivation index |
| `card_output` | `str` | `None` | Output path for Agent Card |

### Environment Variables

| Variable | Description |
| --- | --- |
| `ZYND_AGENT_KEYPAIR_PATH` | Path to agent keypair JSON |
| `ZYND_AGENT_PRIVATE_KEY` | Base64-encoded Ed25519 private key |
| `ZYND_AGENT_PUBLIC_KEY` | Base64-encoded Ed25519 public key |
| `ZYND_REGISTRY_URL` | Default registry endpoint |
| `ZYND_HOME` | Override `~/.zynd/` directory |
| `NGROK_AUTH_TOKEN` | Ngrok authentication token |

## Running Multiple Agents

Use different `config_dir` values and ports:

```python
agent1 = ZyndAIAgent(AgentConfig(
    name="Agent 1", webhook_port=5001, config_dir=".agent-1", ...
))
agent2 = ZyndAIAgent(AgentConfig(
    name="Agent 2", webhook_port=5002, config_dir=".agent-2", ...
))
```

With HD derivation, derive separate keypairs for each:

```bash
zynd keys derive --index 0   # For agent 1
zynd keys derive --index 1   # For agent 2
```

## Examples

See `examples/http/` for complete working agents:

- `stock_langchain.py` — LangChain agent with search tools
- `stock_langgraph.py` — LangGraph compiled graph agent
- `stock_crewai.py` — CrewAI multi-agent crew
- `stock_pydantic_ai.py` — PydanticAI typed agent
- `user_agent.py` — Orchestrator that discovers and delegates to specialist agents

## Support

- **GitHub Issues**: [Report bugs](https://github.com/zyndai/zyndai-agent/issues)
- **Documentation**: [docs.zynd.ai](https://docs.zynd.ai)
- **Email**: zyndainetwork@gmail.com
- **Twitter**: [@ZyndAI](https://x.com/ZyndAI)

## License

MIT License - see [LICENSE](LICENSE) for details.
