DEV Community

Michael Kantor
Michael Kantor

Posted on

Discovering MIT NANDA Agents with Registry Broker

NANDA (Networked Agents and Decentralized AI) creates infrastructure for decentralized agents. It builds on protocols like MCP and A2A to handle agent identification and routing.

The Hashgraph Online Registry Broker now includes a NANDA Adapter. This lets you discover and talk to NANDA agents using the same API you use for everything else.

Understanding NANDA

NANDA addresses fundamental questions for autonomous agent systems:

  • How do agents find each other? A decentralized registry provides discoverability without centralized control
  • How do agents establish trust? Verifiable attestations and reputation mechanisms
  • How do agents communicate? Standardized protocols including A2A (Agent-to-Agent) messaging
  • How does the ecosystem scale? Open, extensible architecture supporting diverse agent types

The NANDA framework complements other agent ecosystems like AgentVerse, Virtuals, and MCP by providing a research-oriented, academically-rigorous approach to agent infrastructure.

Prerequisites

Install the standards SDK:

npm install @hashgraphonline/standards-sdk dotenv
Enter fullscreen mode Exit fullscreen mode

Configure your environment:

# .env
REGISTRY_BROKER_BASE_URL=https://hol.org/registry/api/v1
Enter fullscreen mode Exit fullscreen mode

Discovering NANDA Agents

Search for agents registered in the NANDA ecosystem:


const client = new RegistryBrokerClient({ 
  baseUrl: process.env.REGISTRY_BROKER_BASE_URL 
    ?? 'https://hol.org/registry/api/v1' 
});

async function discoverNandaAgents(): Promise<void> {
  const results = await client.search({
    registries: ['nanda'],
    limit: 10,
  });

  console.log(`Found ${results.total} NANDA agents:`);

  for (const agent of results.hits) {
    console.log(`\n${agent.name}`);
    console.log(`  UAID: ${agent.uaid}`);
    console.log(`  Available: ${agent.available ?? 'unknown'}`);

    // Check for A2A endpoint
    const endpoints = agent.endpoints as Record<string, unknown> | undefined;
    if (endpoints) {
      const a2aEndpoint = extractA2aEndpoint(endpoints);
      if (a2aEndpoint) {
        console.log(`  A2A Endpoint: ${a2aEndpoint}`);
      }
    }
  }
}

function extractA2aEndpoint(
  endpoints: Record<string, unknown>
): string | undefined {
  // Check custom endpoints first
  const custom = endpoints.customEndpoints as Record<string, unknown> | undefined;
  if (custom?.a2a && typeof custom.a2a === 'string') {
    return custom.a2a;
  }

  // Fall back to primary endpoint
  if (typeof endpoints.api === 'string') {
    return endpoints.api;
  }

  return undefined;
}
Enter fullscreen mode Exit fullscreen mode

Communicating with NANDA Agents

NANDA agents support the A2A (Agent-to-Agent) protocol. The Registry Broker handles protocol translation, allowing you to use the standard chat API:

async function chatWithNandaAgent(uaid: string): Promise<void> {
  console.log(`Starting conversation with NANDA agent: ${uaid}`);

  // Send a message through the broker
  const response = await client.chat.sendMessage({
    uaid,
    message: 'Hello! What capabilities do you have?',
  });

  console.log(`Response: ${response.message}`);
  console.log(`Session: ${response.sessionId}`);

  // Continue the conversation
  const followUp = await client.chat.sendMessage({
    uaid,
    sessionId: response.sessionId,
    message: 'Can you help me with a simple task?',
  });

  console.log(`Follow-up: ${followUp.message}`);
}
Enter fullscreen mode Exit fullscreen mode

Direct A2A Communication

For advanced use cases, you can communicate directly with NANDA agents using the A2A JSON-RPC protocol:

interface A2AResponse {
  ok: boolean;
  status: number;
  body: unknown;
}

async function sendA2aMessage(
  endpoint: string, 
  message: string,
  timeoutMs: number = 30000
): Promise<A2AResponse> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: `nanda-${Date.now()}`,
        method: 'message/send',
        params: {
          message: {
            role: 'user',
            parts: [{ kind: 'text', text: message }],
          },
        },
      }),
      signal: controller.signal,
    });

    const body = await response.json();
    return { ok: response.ok, status: response.status, body };
  } finally {
    clearTimeout(timer);
  }
}

// Usage
async function directA2aExample(): Promise<void> {
  // First, discover a NANDA agent
  const results = await client.search({
    registries: ['nanda'],
    limit: 1,
  });

  if (results.hits.length === 0) {
    console.log('No NANDA agents found');
    return;
  }

  const agent = results.hits[0];
  const endpoints = agent.endpoints as Record<string, unknown>;
  const a2aEndpoint = extractA2aEndpoint(endpoints);

  if (!a2aEndpoint) {
    console.log('Agent has no A2A endpoint');
    return;
  }

  // Normalize endpoint to include /a2a path
  const normalizedEndpoint = a2aEndpoint.endsWith('/a2a') 
    ? a2aEndpoint 
    : `${a2aEndpoint.replace(/\/$/, '')}/a2a`;

  console.log(`Sending direct A2A message to: ${normalizedEndpoint}`);

  const response = await sendA2aMessage(
    normalizedEndpoint,
    'Hello from direct A2A communication!'
  );

  console.log(`Status: ${response.status}`);
  console.log(`Body: ${JSON.stringify(response.body, null, 2)}`);
}
Enter fullscreen mode Exit fullscreen mode

Building an A2A Forwarding Agent

You can create a local agent that forwards requests through the Registry Broker to NANDA agents:


interface ForwardingAgentConfig {
  host: string;
  port: number;
  targetUaid: string;
  brokerClient: RegistryBrokerClient;
}

async function startForwardingAgent(config: ForwardingAgentConfig) {
  const { host, port, targetUaid, brokerClient } = config;

  const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
    // Only handle POST /a2a
    if (req.method !== 'POST' || !req.url?.includes('/a2a')) {
      res.writeHead(404, { 'content-type': 'application/json' });
      res.end(JSON.stringify({ error: 'not_found' }));
      return;
    }

    try {
      // Collect request body
      const body = await collectRequestBody(req);
      const payload = JSON.parse(body);

      // Extract message text from A2A format
      const text = payload?.params?.message?.parts?.[0]?.text 
        ?? payload?.params?.message?.content 
        ?? 'Hello';

      // Forward through the broker
      const brokerResponse = await brokerClient.chat.sendMessage({
        uaid: targetUaid,
        message: text,
      });

      // Return A2A formatted response
      res.writeHead(200, { 'content-type': 'application/json' });
      res.end(JSON.stringify({
        jsonrpc: '2.0',
        id: payload?.id ?? null,
        result: {
          kind: 'message',
          role: 'agent',
          parts: [{ kind: 'text', text: brokerResponse.message }],
        },
      }));
    } catch (error) {
      res.writeHead(200, { 'content-type': 'application/json' });
      res.end(JSON.stringify({
        jsonrpc: '2.0',
        id: null,
        error: {
          code: -32603,
          message: error instanceof Error ? error.message : 'Unknown error',
        },
      }));
    }
  });

  await new Promise<void>((resolve, reject) => {
    server.once('error', reject);
    server.listen(port, host, resolve);
  });

  return {
    endpoint: `http://${host}:${port}/a2a`,
    stop: () => new Promise<void>((resolve) => server.close(() => resolve())),
  };
}

function collectRequestBody(req: IncomingMessage): Promise<string> {
  return new Promise((resolve, reject) => {
    let data = '';
    req.setEncoding('utf8');
    req.on('data', chunk => { data += chunk; });
    req.on('end', () => resolve(data));
    req.on('error', reject);
  });
}
Enter fullscreen mode Exit fullscreen mode

Agent Availability and Trust Signals

The Registry Broker tracks availability and trust metrics for NANDA agents:

async function checkAgentStatus(uaid: string): Promise<void> {
  // Resolve full agent details
  const resolved = await client.resolveUaid(uaid);
  const agent = resolved.agent;

  console.log(`Agent: ${agent.name}`);
  console.log(`Available: ${agent.available ?? 'unknown'}`);
  console.log(`Availability Status: ${agent.availabilityStatus ?? 'unknown'}`);

  if (agent.availabilityCheckedAt) {
    console.log(`Last Checked: ${agent.availabilityCheckedAt}`);
  }

  // The broker also evaluates agents through simple evaluations
  // (math and science questions) to verify they're responsive
  const metadata = agent.metadata as Record<string, unknown> | undefined;
  const additional = metadata?.additional as Record<string, unknown> | undefined;

  if (additional?.nandaSimpleMathScore !== undefined) {
    console.log(`Math Eval Score: ${additional.nandaSimpleMathScore}`);
    console.log(`Math Eval Status: ${additional.nandaSimpleMathStatus}`);
  }

  if (additional?.nandaSimpleScienceScore !== undefined) {
    console.log(`Science Eval Score: ${additional.nandaSimpleScienceScore}`);
    console.log(`Science Eval Status: ${additional.nandaSimpleScienceStatus}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Complete Working Example

Here's a complete example that discovers a NANDA agent and has a conversation:

import 'dotenv/config';

async function nandaDemo(): Promise<void> {
  const client = new RegistryBrokerClient({
    baseUrl: process.env.REGISTRY_BROKER_BASE_URL 
      ?? 'https://hol.org/registry/api/v1',
  });

  console.log('πŸ”Ž Searching for NANDA agents...');

  const results = await client.search({
    registries: ['nanda'],
    limit: 5,
  });

  if (results.hits.length === 0) {
    console.log('No NANDA agents found in the registry.');
    return;
  }

  // Prefer an available agent
  const agent = results.hits.find(h => h.available === true) 
    ?? results.hits[0];

  console.log(`\nπŸ“‡ Selected agent: ${agent.name}`);
  console.log(`   UAID: ${agent.uaid}`);

  console.log('\nπŸ’¬ Starting conversation...');

  const response = await client.chat.sendMessage({
    uaid: agent.uaid,
    message: 'Hello! Please introduce yourself briefly.',
  });

  console.log(`\nπŸ€– Agent response: ${response.message}`);
  console.log(`   Session: ${response.sessionId}`);

  // Get conversation history
  const history = await client.chat.getHistory(response.sessionId);
  console.log(`\nπŸ“œ History: ${history.history.length} messages`);

  console.log('\nβœ… Demo complete');
}

nandaDemo().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

The Open Registry Vision

NANDA represents an important piece of the broader agent infrastructure puzzle. By integrating NANDA into the Registry Broker, we're demonstrating how diverse agent ecosystems can work together:

  • MIT's NANDA provides academic rigor and decentralized architecture
  • Fetch.ai's AgentVerse offers mature autonomous commerce
  • Virtuals Protocol enables tokenized agent ownership
  • Anthropic's MCP standardizes tool access for LLMs
  • Hedera's HCS-10 provides high-throughput consensus messaging

The Registry Broker serves as the connective tissueβ€”a universal index that respects each ecosystem's unique properties while enabling unified discovery and communication.

Your agents can discover and interact with NANDA agents alongside agents from any other integrated ecosystem, all through the same API.

Top comments (0)