TS

TypeScript SDK

v1.2.x · MIT License · ESM · Node.js 22+

The reference implementation of the Aroha Protocol. 20 packages covering every protocol primitive — transport, identity, orchestration, credentials, settlement, reputation, and more.

Quick start — @aroha-sdk/run

The fastest path to a running agent. @aroha-sdk/run is a zero-dependency package that wraps any function into an Aroha-compatible HTTP server. No identity keys, no envelopes, no ceremony.

npm install @aroha-sdk/run
import { serve } from "@aroha-sdk/run";

const agent = serve("My Agent", async ({ message }) => {
  return `Echo: ${message}`;  // return a string, or yield chunks for streaming
});

agent.start(8000);
// POST http://localhost:8000/v1/run  { message, sessionId?, stream? }
// GET  http://localhost:8000/health
// GET  http://localhost:8000/.well-known/aroha-agent.json
Edge runtimes (Cloudflare Workers / Vercel Edge): export agent.fetch as the default handler instead of calling agent.start(). It is a standard Web API Request → Response function.

Streaming

const agent = serve("Streaming Agent", async function* ({ message }) {
  const words = `Echo: ${message}`.split(" ");
  for (const word of words) {
    yield word + " ";
    await new Promise((r) => setTimeout(r, 50));
  }
});

Bearer auth

const agent = serve("Secure Agent", handler, {
  auth:         "bearer",
  bearerTokens: [process.env.AGENT_TOKEN!],
  // or: verifyBearer: async (token) => myDb.isValid(token),
});
// Throws at startup if auth="bearer" and no tokens are configured.

Signed callers (aroha-sig)

A bearer token proves someone holds a secret, not who they are — every holder of a leaked token is indistinguishable. auth: "aroha-sig" requires each request to be Ed25519-signed by the calling agent's DID key, so you can restrict access to named agents. Requires @aroha-sdk/run@1.3.0.

const agent = serve("Private Agent", handler, {
  auth: "aroha-sig",
  allowedCallers: ["did:aroha:alice", "did:aroha:ci-pipeline"],
  // resolveCallerKey defaults to the Aroha registry.
  // Omit allowedCallers to accept any verifiable caller (attribution only).
});

Callers pass their identity and the SDK signs each request:

import { callAgent, streamAgent } from "@aroha-sdk/run";

const { message } = await callAgent(didHash, "Hello!", {
  signAs: { did: "did:aroha:alice", privateKey: alicePrivateKey },
});

// streamAgent takes the same option

The wire format matches the Python SDK exactly, so either language can call the other. See Private Agents for the full model, including registry visibility and access grants.

Hub registration + callAgent

// Register
const { didHash } = await agent.register("https://my-agent.fly.dev", {
  apiKey: process.env.AROHA_API_KEY,  // from Studio → API Keys
});

// Call any agent by didHash
import { callAgent } from "@aroha-sdk/run";
const { message, sessionId } = await callAgent(didHash, "Hello!", {
  bearerToken: process.env.THEIR_TOKEN,
});

MCP servers and skills

The registry indexes MCP servers and skills alongside agents, discoverable the same way. Both require an explicit did — the registry rejects a registration without one — generated for you if omitted. Requires @aroha-sdk/run@1.3.1.

import { registerMcp, resolveMcp, listMcps } from "@aroha-sdk/run";

const { didHash } = await registerMcp({
  name: "search-mcp",
  endpointUrl: "https://mcp.example.com",
  tools: ["search", "fetch"],  // names, or full { name, inputSchema } objects
  apiKey: process.env.AROHA_API_KEY!,
});

const mcp = await resolveMcp(didHash);
const all = await listMcps({ limit: 20 });

Skills use the same pattern. skillType must be one of the values in SKILL_TYPES (rag, web-search, memory, code, data, custom — default).

import { registerSkill } from "@aroha-sdk/run";

await registerSkill({
  name: "my-rag-skill",
  skillType: "rag",
  apiKey: process.env.AROHA_API_KEY!,
});

Registered MCP servers keep their own auth model — a Gmail or Stripe MCP still needs the calling agent to hold a valid OAuth token or API key for that vendor. Registration makes a server discoverable and correctly wired, not credential-free.

Calling agents

Use callAgent to call any Hub-registered agent by its didHash. The helper resolves the endpoint from the registry and POSTs to /v1/run automatically.

import { callAgent, listAgents, resolveAgent } from "@aroha-sdk/run";

// Call an agent
const { message } = await callAgent("abc123", "What is the capital of France?");

// List agents (optional org filter)
const agents = await listAgents({ org: "studio:abc123", limit: 20 });

// Resolve metadata only
const record = await resolveAgent("abc123");
console.log(record.endpointUrl, record.manifestCID);

Installation — full SDK

Install only what you need. Every package is independently versioned.

# Minimum — agent transport + identity
npm install @aroha-sdk/core

# Full stack — add orchestration, auth, and payment
npm install @aroha-sdk/core @aroha-sdk/orchestrator @aroha-sdk/credentials @aroha-sdk/settlement

# Personal agent bridges
npm install @aroha-sdk/hermes-bridge    # Hermes Agent / ZeroClaw (MCP stdio)
npm install @aroha-sdk/openclaw-bridge  # OpenClaw skills
npm install @aroha-sdk/composio-bridge  # TrustClaw / Composio actions

# Framework bridges
npm install @aroha-sdk/langchain-bridge # LangChain + AutoGen
npm install @aroha-sdk/mcp-bridge       # Model Context Protocol
npm install @aroha-sdk/a2a-bridge       # Google A2A
All packages are pure ESM. Set "type": "module" in your package.json or use .mjs extensions.
npm audit warnings: The SDK's optional dependencies (AutoGen, CrewAI bridges) may trigger moderate severity audit warnings on transitive sub-dependencies. These do not affect the core runtime. Run npm audit --omit=optional to see only warnings relevant to packages you install directly.
Using the TypeScript source directly? Each package must be compiled before it can be imported. From the monorepo root: npm run build --workspaces, or per-package: cd packages/aroha-core && npm run build. The compiled output lands in dist/ — the main field in each package.json points there.

Core: ArohaServer & ArohaClient

@aroha-sdk/core is the only required package. It handles the full protocol stack: HTTP transport, Ed25519 envelope signing/verification, nonce replay protection, and WebSocket streaming.

Minimal agent (devMode)

import { ArohaServer, generateDid, generateKeyPair, toMultibase, MapNonceStore } from "@aroha-sdk/core";

// Generate a keypair and DID (persist these in production)
const { privateKey, publicKey } = await generateKeyPair();
const myDID = generateDid(publicKey);

const server = new ArohaServer({
  agentDID: myDID,
  didDocument: {
    "@context": ["https://www.w3.org/ns/did/v1"],
    id: myDID,
    verificationMethod: [{
      id: `${myDID}#key-1`,
      type: "Ed25519VerificationKey2020",
      controller: myDID,
      publicKeyMultibase: toMultibase(publicKey),  // base58btc multibase ('z' prefix)
    }],
  },
  port: 3000,
  devMode: true,           // skip crypto in development (NODE_ENV=development required)
  onMessage: async (envelope, respond, stream) => {
    console.log("Received:", envelope.type, "from", envelope.from);
    // respond() sends a synchronous reply
    // stream() pushes events over WebSocket
  },
  resolvePublicKey: async (did) => {
    // Return the sender's Ed25519 public key bytes, or null if unknown
    return null;
  },
});

await server.start();
console.log(`Agent ${myDID} listening on port 3000`);

Production server (full crypto)

import { ArohaServer, generateDid, generateKeyPair } from "@aroha-sdk/core";
import { createRbacMiddleware } from "@aroha-sdk/credentials";

const { privateKey, publicKey } = await generateKeyPair();
const myDID = generateDid(publicKey);

const server = new ArohaServer({
  agentDID: myDID,
  didDocument: { /* ... */ },
  port: 3000,
  resolvePublicKey: async (did) => {
    // Fetch from your registry or cache
    const doc = await fetch(`https://aroha-registry.aroha-labs.workers.dev/dids/${did}`);
    const { publicKey } = await doc.json();
    return Buffer.from(publicKey, "base64");
  },
  middleware: [
    // RBAC: require the caller to hold a valid SpendingMandate
    createRbacMiddleware({ requiredTrustLevel: 2 }),
  ],
  clockToleranceMs: 5000,    // allow 5s clock skew across regions
  maxBodyBytes: 1_048_576,   // reject payloads > 1 MiB
  minClientVersion: "1.0",   // reject old client versions
  onMessage: async (envelope, respond) => {
    if (envelope.type === "ArohaRequest") {
      const { capability, params } = envelope.body;
      // handle capability...
      respond(await buildEnvelope("ArohaResponse", myDID, envelope.from,
        { capability, result: { ok: true } },
        envelope.correlationId, privateKey));
    }
  },
});

await server.start();

ArohaClient — calling other agents

import { ArohaClient, buildEnvelope, newCorrelationId } from "@aroha-sdk/core";

const client = new ArohaClient();

const envelope = await buildEnvelope(
  "ArohaRequest",
  myDID,                          // from
  "did:aroha:travel-agent",       // to
  { capability: "search-flights", params: { from: "JFK", to: "LHR", date: "2026-08-01" } },
  newCorrelationId(),             // unique ID for this request
  privateKey                      // signs the envelope
);

const response = await client.send("http://travel-agent.example.com", envelope);

if (response?.type === "ArohaResponse") {
  console.log(response.body.result);   // { flights: [...] }
}
if (!response) {
  // Agent returned 202 — response will arrive via WebSocket stream
}

Identity & DIDs

import { generateKeyPair, generateDid, buildWebDID } from "@aroha-sdk/core";

// did:aroha: (self-sovereign, content-addressed)
const { privateKey, publicKey } = await generateKeyPair();
const did = generateDid(publicKey);
// → "did:aroha:base58encodedPublicKey"

// did:aroha-web: (domain-anchored, like did:web:)
const webDID = buildWebDID("myco.ai", ["agents", "travel"]);
// → "did:aroha-web:myco.ai:agents:travel"

// Serve the DID document at /.well-known/aroha/agents/travel/did.json
// The ArohaServer does this automatically when webDIDDocument is set.

Envelopes & Message Types

Every Aroha message is a signed envelope. The 16 built-in types map to the full protocol lifecycle:

ArohaRequestOrchestrator → Provider: invoke a capability
ArohaResponseProvider → Orchestrator: synchronous result
ArohaStreamProvider → Orchestrator: progress update (WebSocket)
ArohaErrorEither direction: structured error with retryable flag
ArohaReserveSaga step 1: reserve resources, return commitToken
ArohaCommitSaga step 2: commit reserved resources
ArohaCancelSaga compensation: release reserved resources
ArohaHandoffTransfer control to another agent
ArohaDiscoveryQuery the registry for agents with a capability
ArohaCapabilityOfferAgent advertises a capability to another
ArohaAuditTrailAppend-only audit event (hash-chained)
ArohaReputationUpdateSignal: satisfied / neutral / dissatisfied
import { buildEnvelope, newCorrelationId } from "@aroha-sdk/core";

// All envelope fields are set automatically:
//   id          → urn:uuid:<random>
//   created     → now
//   expires     → now + 5 min (default)
//   nonce       → random 16 bytes (base64url)
//   proof       → Ed25519 signature over canonical JSON

const env = await buildEnvelope(
  "ArohaReserve",           // type
  orchestratorDID,          // from
  providerDID,              // to
  {
    capability: "book-flight",
    params: { from: "JFK", to: "LHR" },
    budgetUsd: 500,
  },
  newCorrelationId(),       // correlationId (ties Reserve→Commit→Cancel)
  orchestratorPrivateKey
);

SagaEngine & Orchestration

The @aroha-sdk/orchestrator package provides atomic multi-agent transactions using the saga pattern: Reserve → Commit → Cancel (LIFO compensation on failure).

import { SagaEngine, AgentSelector } from "@aroha-sdk/orchestrator";
import { ArohaClient } from "@aroha-sdk/core";

const saga = new SagaEngine({ client, orchestratorDID, privateKey });

// Each step is: reserve with one agent, then another, then commit all or cancel all
const result = await saga.run([
  {
    agentDID: "did:aroha:flight-agent",
    endpoint: "http://flights.example.com",
    capability: "reserve-flight",
    params: { from: "JFK", to: "LHR", date: "2026-08-01" },
    budgetUsd: 300,
  },
  {
    agentDID: "did:aroha:hotel-agent",
    endpoint: "http://hotels.example.com",
    capability: "reserve-hotel",
    params: { city: "London", nights: 3 },
    budgetUsd: 200,
  },
]);

// If hotels.example.com fails: flight reservation is automatically cancelled
// result.status → "committed" | "compensated"
// result.steps[n].commitToken → use for billing reconciliation

AgentSelector — pick the best agent

import { AgentSelector } from "@aroha-sdk/orchestrator";
import { ArohaHttpRegistry } from "@aroha-sdk/registry";

const registry = new ArohaHttpRegistry("https://aroha-registry.aroha-labs.workers.dev");
const selector = new AgentSelector(registry);

// Discovers agents with "search-flights", ranks by reputation + latency + price
const best = await selector.select("search-flights", {
  maxLatencyMs: 2000,
  maxPriceUsd: 0.01,
  minTrustLevel: 2,
});
console.log(best.manifest.did, best.endpoint);

Credentials & RBAC

import {
  issueIntentMandate,
  verifyMandate,
  createRbacMiddleware,
  attenuateToCart,
  attenuateToPayment,
  ArohaRole,
} from "@aroha-sdk/credentials";

// Issue a mandate: user authorises the orchestrator to spend up to $500
// Signature: issueIntentMandate(grantorDID, granteeDID, constraints, privateKey, ttlMs?)
const mandate = await issueIntentMandate(
  userDID,
  orchestratorDID,
  { spendLimitUsd: 500, currency: "USD" },
  userPrivateKey,
  3_600_000,   // 1 hour TTL
);
// mandate → { mandate, signature, token }

// Attenuate step 1: narrow to a specific provider (must be ≤ original limits)
// attenuateToCart(parent, granteeDID, narrowedConstraints, grantorPrivateKey, ttlMs?)
const cartMandate = await attenuateToCart(
  mandate,
  flightAgentDID,
  { spendLimitUsd: 300, currency: "USD" },
  orchestratorPrivateKey,
);

// Attenuate step 2: lock down to a single payment amount
// attenuateToPayment(parent, granteeDID, narrowedConstraints, grantorPrivateKey, ttlMs?)
const payMandate = await attenuateToPayment(
  cartMandate,
  flightAgentDID,
  { spendLimitUsd: 299 },
  orchestratorPrivateKey,
);

// Verify on the receiver side
// verifyMandate(token, publicKey, expectedGrantee?) → { valid, mandate?, reason? }
const { valid, mandate: decoded, reason } = await verifyMandate(
  payMandate.token,
  userPublicKey,
);

// On the server — enforce RBAC on every inbound message
const agentRoles = new Map([
  [orchestratorDID, [ArohaRole.AgentOrchestrator]],
]);
const server = new ArohaServer({
  middleware: [
    createRbacMiddleware({
      resolveAgentRoles: async (did) => agentRoles.get(did) ?? [],
    }),
  ],
  // ...
});

Settlement

import { StripeSettlement, NullSettlement, EscrowSettlement, QuotaSettlement } from "@aroha-sdk/settlement";
import { SagaEngine } from "@aroha-sdk/orchestrator";

// Stripe backend — authorises on Reserve, captures on Commit, voids on Cancel
const settlement = new StripeSettlement({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  resolveCharge: async (ctx) => {
    if (ctx.capability === "book-flight")
      return { amountCents: 45000, currency: "usd", customerId: "cus_abc" };
    return null;  // free capability
  },
});

// Inject into SagaEngine — settlement is a saga concern, not a server concern
const saga = new SagaEngine({
  client,
  orchestratorDID: identity.did,
  privateKey: identity.privateKey,
  settlement,    // ← here
});

// Available backends:
// NullSettlement     — free tier / dev (default)
// QuotaSettlement    — internal credit system
// StripeSettlement   — manual-capture card charges
// EscrowSettlement   — hold funds in escrow until Commit
// ApiKeySettlement   — debit a pre-paid API credit balance
// AP2Settlement      — ES256 SD-JWT mandates for Google's Agent Payments Protocol (alpha)

Agent Registry

import { InMemoryRegistry, ArohaHttpRegistry } from "@aroha-sdk/registry";

// Development: in-process registry
const registry = new InMemoryRegistry();
registry.register({
  did: myDID,
  endpoint: "http://localhost:3000",
  capabilities: [
    { id: "search-flights", description: "Search available flights" },
  ],
});

// Production: federated HTTP registry
const registry = new ArohaHttpRegistry("https://aroha-registry.aroha-labs.workers.dev");

// Discover agents
const agents = await registry.find({ capability: "search-flights" });
// → [{ manifest: { did, capabilities }, endpoint }]

Framework Bridges

Hermes Agent & ZeroClaw (MCP stdio)

// hermes.config.json  (or zeroclaw config.toml)
{
  "mcpServers": {
    "aroha-travel": {
      "command": "npx",
      "args": [
        "@aroha-sdk/hermes-bridge",
        "--endpoint", "http://travel-agent.example.com",
        "--agent-did", "did:aroha:travel-agent"
      ]
    }
  }
}

OpenClaw

// openclaw-plugin.mjs
import { createArohaOpenClawPlugin } from "@aroha-sdk/openclaw-bridge";
import { generateKeyPair, generateDid } from "@aroha-sdk/core";

const { privateKey, publicKey } = await generateKeyPair();
const myDID = generateDid(publicKey);

export default createArohaOpenClawPlugin("aroha-travel", [
  {
    capabilityId: "search-flights",
    endpoint: "http://travel-agent.example.com",
    agentDID: "did:aroha:travel-agent",
    callerDID: myDID,
    callerPrivateKey: privateKey,
    description: "Search and book flights via Aroha travel agent",
    parameters: {
      from: { type: "string", description: "Origin IATA code (e.g. JFK)" },
      to:   { type: "string", description: "Destination IATA code (e.g. LHR)" },
      date: { type: "string", description: "Departure date YYYY-MM-DD" },
    },
  },
]);

Composio / TrustClaw

import { Composio } from "composio-core";
import { registerArohaCapabilities } from "@aroha-sdk/composio-bridge";
import { generateKeyPair, generateDid } from "@aroha-sdk/core";

const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
const { privateKey, publicKey } = await generateKeyPair();
const myDID = generateDid(publicKey);

await registerArohaCapabilities(composio, [
  {
    endpoint: "http://travel-agent.example.com",
    agentDID: "did:aroha:travel-agent",
    capabilityId: "search-flights",
    description: "Search for available flights",
    callerDID: myDID,
    callerPrivateKey: privateKey,
    parameters: {
      from: { type: "string", description: "Origin IATA code" },
      to:   { type: "string", description: "Destination IATA code" },
    },
    required: ["from", "to"],
  },
]);
// Now TrustClaw, Claude+Composio, GPT-4+Composio can all call this

LangChain / AutoGen

import { arohaCapabilityToLangChainTool, arohaCapabilityToOpenAITool } from "@aroha-sdk/langchain-bridge";

// LangChain tool
const flightTool = arohaCapabilityToLangChainTool("search-flights", {
  endpoint: "http://travel-agent.example.com",
  agentDID: "did:aroha:travel-agent",
  callerDID: myDID,
  callerPrivateKey: privateKey,
  description: "Search available flights between airports",
});

// AutoGen / OpenAI function tool
const flightFn = arohaCapabilityToOpenAITool("search-flights", { /* same opts */ });

// Pass to your LangChain agent as a normal tool
const agent = initializeAgentExecutorWithOptions([flightTool], llm, { agentType: "openai-functions" });

All Packages

@aroha-sdk/coreTransport (HTTP + WS), Ed25519, envelopes, nonce registry, DID generation
@aroha-sdk/orchestratorSagaEngine (Reserve→Commit→Cancel), AgentSelector, CSN negotiation
@aroha-sdk/credentialsSpendingMandate issue/attenuation/verify, RBAC middleware
@aroha-sdk/registryAgent discovery — InMemoryRegistry, ArohaHttpRegistry, on-chain registry
@aroha-sdk/settlementPayment backends: Null, Quota, Stripe, Escrow
@aroha-sdk/reputationBayesian Beta engine, Thompson Sampling, satisfaction signals
@aroha-sdk/policyCapability allow/deny rules, prompt injection defense
@aroha-sdk/cacheSemantic request caching keyed by capability + params hash
@aroha-sdk/telemetryOpenTelemetry traces + W3C Trace Context headers
@aroha-sdk/commerceB2B agreements, subscription billing, audit trail
@aroha-sdk/preferencesUser preference profiles, soft-scoring for agent selection
@aroha-sdk/trusted-meshVPC/mTLS trusted mesh — bypass Ed25519 for internal calls
@aroha-sdk/mcp-bridgeMCP tool definitions ↔ Aroha capabilities (bidirectional)
@aroha-sdk/a2a-bridgeGoogle A2A message format ↔ Aroha envelopes
@aroha-sdk/langchain-bridgeLangChain tools ↔ Aroha capabilities, AutoGen + Semantic Kernel
@aroha-sdk/hermes-bridgeHermes Agent + ZeroClaw: MCP stdio server wrapping Aroha agents
@aroha-sdk/openclaw-bridgeOpenClaw skills ↔ Aroha capabilities (bidirectional)
@aroha-sdk/composio-bridgeComposio custom actions for TrustClaw, Claude, GPT-4, Gemini
@aroha-sdk/micro5-line dev server: createMicroAgent({ capabilities, port })
@aroha-sdk/clinpx @aroha-sdk/cli doctor / init — health-check, keypair generation, agent scaffolding