Autonomous Agentic Coordination Framework (WIP)
sentrix is a growing Rust project in the AI payments / x402 ecosystem, focused on a2a, a2a-protocol, agent-discovery, agent-framework. It currently has 26 GitHub stars and 1 forks, and sits alongside related tools like agentanycast, a2a-go, awesome-a2a-libraries, a2a-demos, a2a-elixir, kbs_agent_a2a.
Autonomous Agentic Coordination Middleware — scaffold P2P-discoverable, DID-native AI agents that interoperate across any framework, with optional ERC-8004 on-chain compliance.
இணை (inai) — Tamil for "join" or "connect". Like TCP/IP connects heterogeneous computers, Inai connects heterogeneous agents.
Most AI frameworks help you build agents. Inai helps them find and talk to each other — across frameworks, runtimes, and clouds.
| What exists today | The gap Inai fills |
|---|---|
| Orchestration frameworks (CrewAI, AutoGen) | Agents locked inside one runtime, no external discovery |
| Framework-bound agents (LangGraph, AutoGPT) | No standardised interface for cross-framework calls |
| Closed ecosystems (Fetch.ai, SingularityNET) | Ecosystem lock-in, heavyweight infrastructure |
Inai is not a platform — it is a protocol layer others build on.
| Layer | Role | Technologies |
|---|---|---|
| L4 Execution | Agent frameworks | LangGraph · Google ADK · CrewAI · Agno · LlamaIndex · smolagents · Hermes · OpenAI Agents |
| L3 Interaction | Request / response | AgentRequest / AgentResponse · AMP-2 |
| L2 Discovery | Capability lookup | Local · HTTP · libp2p + Kademlia DHT · AMP-1 |
| L1 Identity | DID + trust | did:key W3C (default) · ERC-8004 on-chain (optional) |
Inai operates primarily at L2 and L3, bridging L1 identity to L4 framework execution.
inai run MyAgent --port 6174 starts a real HTTP server; no extra setupdid:key W3C standard out of the box; no wallet, no gas, no tokens required| Feature | Python | TypeScript | Rust | Zig |
|---|---|---|---|---|
| IAgent interface | ✅ | ✅ | ✅ | ✅ |
| AgentRequest / AgentResponse | ✅ | ✅ | ✅ | ✅ |
| ANR (Agent Network Record) | ✅ | ✅ | ✅ | ✅ |
DID identity (did:key) |
✅ | ✅ | ✅ | ✅ |
HTTP server (inai run) |
✅ | ✅ | ✅ | ✅ |
| Discovery — local (in-memory) | ✅ | ✅ | ✅ | ✅ |
| Discovery — HTTP | ✅ | ✅ | ✅ | ✅ |
| Discovery — libp2p + Kademlia DHT | ✅ | ✅ | ✅ | ✅ |
| Discovery — gossip fan-out | ✅ | ✅ | ✅ | ✅ |
| AgentClient (mesh protocols) | ✅ | ✅ | ✅ | ✅ |
| Example agent | ✅ | ✅ | ✅ | ✅ |
| Plugin system (framework adapters) | ✅ | ✅ | ✅ | ✅ |
| LangGraph plugin | ✅ | ✅ | ✅ | ✅ |
| Google ADK plugin | ✅ | ✅ | ✅ | ✅ |
| CrewAI plugin | ✅ | ✅ | ✅ | ✅ |
| OpenAI Agents SDK plugin | ✅ | ✅ | ✅ | ✅ |
| Agno plugin | ✅ | ✅ | ✅ | ✅ |
| LlamaIndex plugin | ✅ | ✅ | ✅ | ✅ |
| smolagents plugin | ✅ | ✅ | ✅ | ✅ |
Hermes plugin (Nous Research AIAgent) |
✅ | ✅ | ✅ | ✅ |
| MCP bridge (wrap MCP servers) | ✅ | ✅ | ✅ | ✅ |
| MCP bridge (expose as MCP server) | ✅ | ✅ | ✅ | ✅ |
| x402 micropayments | ✅ | ✅ | ✅ | ✅ |
| MPP plugin (Machine Payments Protocol) | 🔜 | ✅ | ✅ | ✅ |
| Streaming (SSE via /invoke/stream) | ✅ | ✅ | ✅ | ✅ |
Legend: ✅ implemented · 🔜 on roadmap · — not applicable for this language
MPP: HTTP 402 payment gating using the MPP challenge–credential–receipt model (MppPlugin / mpp modules). Python template support is on the roadmap; use x402 or bridge via a TS/Rust/Zig agent until then.
Rust / Zig — framework plugins: LangGraph, Google ADK, and CrewAI are Python/JS frameworks with no native Rust or Zig SDKs. The Rust and Zig plugins are HTTP bridge adapters that call a running service endpoint (e.g. adk web, a LangServe app, or a FastAPI-wrapped CrewAI crew) so the agent participates in the Inai mesh without embedding a Python interpreter.
Zig — discovery: HttpDiscovery (discovery_http.zig) is a REST client for the discovery service. Libp2pDiscovery (discovery_libp2p.zig) is a full Kademlia DHT implementation in pure Zig (UDP/JSON transport, 256-bucket XOR routing table, k=20). Key derivation matches the Rust implementation (SHA-256("inai:cap:<cap>") / SHA-256("inai:anr:<agentId>")). Note: uses a Inai-native JSON wire format rather than libp2p protobuf — interoperates with other Zig Inai nodes.
Works on macOS, Linux, and Windows. npm downloads the correct pre-built binary for your platform automatically.
npm install -g @ch4r10teer41/inai-cli
Auto-detects your OS and architecture, installs to /usr/local/bin:
curl -fsSL https://raw.githubusercontent.com/ch4r10t33r/inai/main/install.sh | sh
To install to a custom directory set INAI_INSTALL_DIR before piping:
INAI_INSTALL_DIR=~/.local/bin curl -fsSL https://raw.githubusercontent.com/ch4r10t33r/inai/main/install.sh | sh
Windows — use npm above, or download
inai-win32-x64.exedirectly from the Releases page.
Requires Rust 1.75+.
git clone https://github.com/ch4r10t33r/inai.git
cd inai
cargo build --release --package inai-cli
# Binary is at ./target/release/inai
# Scaffold a new project (TypeScript default)
inai init my-agent
cd my-agent && npm install
inai run ExampleAgent --port 6174
# Python
inai init my-agent --lang python
cd my-agent
inai run ExampleAgent --port 6174
# Rust
inai init my-agent --lang rust
cd my-agent
cargo run --example did_key_identity # optional: did:key from secp256k1 secret
cargo run --example gossip_fanout_discovery # optional: in-memory gossip fan-out demo
# Zig
inai init my-agent --lang zig
cd my-agent
zig build examples # optional: builds did:key + gossip fan-out demo binaries
Once running, the agent prints its full startup banner. libp2p is the default — a Peer ID is shown out of the box:
libp2p mode (default — no config needed):
────────────────────────────────────────────────────────────
Inai Agent Online v0.1.0
────────────────────────────────────────────────────────────
Name ExampleAgent
Agent ID inai://agent/example
Peer ID 12D3KooWQmK3p2pFBFVxpuBkJCxZ1HsK...
Endpoint http://0.0.0.0:6174
Multiaddr /ip4/0.0.0.0/tcp/6174/p2p/12D3KooW...
Discovery libp2p (Kademlia DHT)
ANR {"agentId":"inai://agent/example","name":"ExampleAgent",...}
ANR JSON curl -s http://localhost:6174/anr
Capabilities (2)
• echo
• ping
────────────────────────────────────────────────────────────
Local mode (INAI_DISCOVERY_TYPE=local — dev/test, zero ports opened):
────────────────────────────────────────────────────────────
Inai Agent Online v0.1.0
────────────────────────────────────────────────────────────
Name ExampleAgent
Agent ID inai://agent/example
Peer ID (none — local discovery mode)
Endpoint http://0.0.0.0:6174
Discovery local
ANR {"agentId":"inai://agent/example","name":"ExampleAgent",...}
ANR JSON curl -s http://localhost:6174/anr
Capabilities (2)
• echo
• ping
────────────────────────────────────────────────────────────
Default port: 6174 (Kaprekar's constant). Override with
INAI_PORT=<n>or--port <n>.
Peer ID (none — local discovery mode)
This means INAI_DISCOVERY_TYPE=local is set (or the libp2p host failed to bind and fell back to local). In local mode there is no libp2p host, so no Peer ID exists. The agent is fully functional — it just uses in-process discovery.
libp2p is the default. If you see this line unexpectedly, check that your .env does not have INAI_DISCOVERY_TYPE=local set, or that no other process is occupying the libp2p port.
To ensure a stable Peer ID across restarts, set a persistent key:
INAI_AGENT_KEY=<your 64-hex-char secp256k1 private key>
Generate a fresh key:
# Node.js one-liner
node -e "const {secp256k1} = require('ethereum-cryptography/secp256k1'); \
console.log(Buffer.from(secp256k1.utils.randomPrivateKey()).toString('hex'));"
# or openssl
openssl rand -hex 32
If INAI_AGENT_KEY is omitted a random ephemeral key is generated at startup — the Peer ID will change every time the process restarts.
The startup banner previously only printed the URL to the /anr endpoint, which made it easy to miss the fact that an ANR was live. The banner now always shows:
| Line | What it means |
|---|---|
ANR |
One-line JSON preview: agentId, name, capabilities, network |
ANR JSON |
The exact curl command to fetch the full record |
Fetch the full ANR at any time:
curl -s http://localhost:6174/anr | jq .
The ANR is always present regardless of discovery mode. It is not dependent on libp2p being enabled.
local, http, and libp2p discovery?| Mode | When to use | PeerId | Central server |
|---|---|---|---|
libp2p (default) |
All environments — production P2P mesh | ✓ | ✗ |
local |
Unit tests / single-process demos | ✗ | ✗ |
http |
Staging / enterprise / managed registry | ✗ | ✓ |
Switch modes with a single env var — no code changes required:
# libp2p (default — nothing to set, or explicitly):
INAI_DISCOVERY_TYPE=libp2p
INAI_AGENT_KEY=<64-hex secp256k1 private key> # optional but recommended
# local (dev/test — zero ports):
INAI_DISCOVERY_TYPE=local
# http (centralised registry):
INAI_DISCOVERY_TYPE=http
INAI_DISCOVERY_URL=https://registry.example.com
| Command | Description |
|---|---|
inai scaffold <name> [OPTIONS] |
Generate a minimal, targeted agent project (see below) |
inai init <name> [--lang ts|python|rust|zig] |
Copy the full template library into a new project (see below) |
inai create agent <name> [-c cap1,cap2] [--framework X] |
Add an agent to an existing project (X: none, google-adk, crewai, langgraph, agno, llamaindex, smolagents, hermes) |
inai run <AgentName> [--port 6174] |
Start an agent's HTTP server |
inai discover [-c capability] [--host h] [--port p] |
Query the discovery layer |
inai version |
Show CLI version and build info |
inai scaffold vs inai initBoth create a new agent project, but they serve different workflows:
scaffold |
init |
|
|---|---|---|
| Approach | Generates files programmatically from flags | Copies the full embedded template library |
| Output | Minimal — only what you asked for | Full kitchen sink — all discovery adapters, example agents, every template file |
| Customisation | --plugins, --stream, --x402, --did, --discovery flags wire things together for you |
Raw templates with {{AGENT_NAME}} token substitution — you wire things yourself |
| Also generates | .env.example, README.md |
inai.config.json |
| Best for | Starting a focused, production-ready agent quickly | Exploring the full template library or building something custom |
| Languages | TypeScript, Rust, Zig | TypeScript, Python, Rust, Zig |
Rule of thumb: use
scaffoldwhen you know what you want; useinitwhen you want to browse all available patterns and pick your own path.
inai scaffold — targeted project generatorinai scaffold <name> [OPTIONS]
Options:
-l, --lang <LANG> typescript | rust | zig [default: typescript]
-p, --plugins <PLUGINS> openai,agno,langgraph,google_adk,crewai,
llamaindex,smolagents,mcp [default: none]
-o, --output <DIR> output directory [default: cwd]
-d, --did include DID key generation example
-s, --stream include SSE streaming endpoint
-x, --x402 include x402 micropayments middleware
--discovery <BACKEND> http | libp2p [default: http]
--dry-run print file tree without writing
Examples:
# TypeScript agent with LangGraph + OpenAI, SSE streaming, libp2p discovery
inai scaffold my-agent --lang typescript --plugins langgraph,openai --stream --discovery libp2p
# Rust agent with MCP bridge and x402 micropayments
inai scaffold payments-agent --lang rust --plugins mcp --x402
# Zig agent with DID examples — preview first, then generate
inai scaffold did-agent --lang zig --did --dry-run
inai scaffold did-agent --lang zig --did
Generated structure (TypeScript example):
my-agent/
├── package.json
├── tsconfig.json
├── src/
│ ├── agent.ts ← discovery registration, /invoke handler, selected plugins
│ ├── index.ts ← entry point
│ └── plugins/ ← only created when --plugins is set
│ └── LangGraphPlugin.ts
├── .env.example
└── README.md
inai init — full template copyinai init <name> [OPTIONS]
Options:
-l, --lang <LANG> typescript | python | rust | zig [default: typescript]
--no-discovery skip copying discovery adapter files
--no-example skip copying example agent files
Copies the entire template library for the chosen language into <name>/, applies token substitution ({{AGENT_NAME}}, {{PROJECT_NAME}}, etc.), and writes a inai.config.json. The result is a fully-populated project containing every discovery adapter, all plugin stubs, and complete example agents — ready to explore and trim down.
# Full TypeScript project with everything included
inai init my-project --lang typescript
# Rust project without the example agent files
inai init my-project --lang rust --no-example
inai run — HTTP endpointsWhen you run an agent, these endpoints are live automatically:
| Endpoint | Method | Description |
|---|---|---|
/invoke |
POST |
Call any capability — { capability, payload, from } → AgentResponse |
/invoke/stream |
POST |
Same as /invoke but responds with text/event-stream SSE frames |
/health |
GET |
Heartbeat — returns health status, capability count, version |
/anr |
GET |
Full Agent Network Record (ANR) as JSON |
/capabilities |
GET |
List of capability names |
/gossip |
POST |
Receive gossip messages from mesh peers |
# Google ADK
from plugins.google_adk_plugin import wrap_google_adk
agent = wrap_google_adk(adk_agent, name="SupportBot", agent_id="inai://agent/support", owner="0x...")
# CrewAI
from plugins.crewai_plugin import wrap_crewai
agent = wrap_crewai(crew_agent, name="ResearchBot", agent_id="inai://agent/research", owner="0x...")
# LangGraph
from plugins.langgraph_plugin import wrap_langgraph
agent = wrap_langgraph(compiled_graph, config)
# OpenAI Agents SDK
from plugins.openai_plugin import wrap_openai
agent = wrap_openai(oai_agent, name="WeatherBot", agent_id="inai://agent/weather", owner="0x...")
# Agno / LlamaIndex / smolagents
from plugins.agno_plugin import wrap_agno
from plugins.llamaindex_plugin import wrap_llamaindex
from plugins.smolagents_plugin import wrap_smolagents
# Hermes (Nous Research) — Python only; pip install git+https://github.com/NousResearch/hermes-agent.git
from run_agent import AIAgent
from plugins.hermes_plugin import wrap_hermes
_hermes = AIAgent(model="openai/gpt-4o-mini", quiet_mode=True, skip_memory=True, skip_context_files=True)
agent = wrap_hermes(_hermes, name="HermesBot", agent_id="inai://agent/hermes", owner="0x...", mesh_capabilities=["chat", "research"])
# Serve over HTTP (all plugins share the same interface)
import asyncio
asyncio.run(agent.serve(port=6174))
// LangGraph (in-process — wraps a compiled CompiledGraph)
import { wrapLangGraph } from './plugins/LangGraphPlugin';
const agent = wrapLangGraph(compiledGraph, { agentId: 'inai://agent/researcher', name: 'Researcher', ... });
// Google ADK (in-process — wraps a BaseAgent / LlmAgent)
import { wrapGoogleADK } from './plugins/GoogleADKPlugin';
const agent = wrapGoogleADK(adkAgent, { agentId: 'inai://agent/support', name: 'Support', ... });
// CrewAI (HTTP bridge — calls a running CrewAI service)
import { wrapCrewAI } from './plugins/CrewAIPlugin';
const agent = await wrapCrewAI({
agentId: 'inai://agent/writer',
name: 'WriterCrew',
version: '1.0.0',
owner: '0xYourWallet',
serviceUrl: 'http://localhost:8000', // FastAPI-wrapped CrewAI crew
});
// OpenAI Agents SDK
import { wrapOpenAI } from './plugins/OpenAIPlugin';
import { AgnoPlugin } from './plugins/AgnoPlugin';
import { LlamaIndexPlugin } from './plugins/LlamaIndexPlugin';
import { SmolagentsPlugin } from './plugins/SmolagentsPlugin';
const agent = wrapOpenAI(oaiAgent, { agentId: 'inai://agent/weather', name: 'WeatherBot', ... });
// Agno, LlamaIndex, smolagents — same one-liner pattern
const agnoAgent = new AgnoPlugin({ agentId: 'inai://agent/agno', ... }).wrap(myAgnoAgent);
const llamaAgent = new LlamaIndexPlugin({ agentId: 'inai://agent/llama', ... }).wrap(myIndex);
const smolaAgent = new SmolagentsPlugin({ agentId: 'inai://agent/smol', ... }).wrap(mySmolAgent);
// Hermes (Nous Research) — use `plugins/hermes_plugin.py` + `inai create agent ... --lang python --framework hermes`
await agent.serve({ port: 6174 });
use inai::plugins::{
langgraph::{LangGraphPlugin, LangGraphService},
google_adk::{GoogleADKPlugin, GoogleADKService},
crewai::{CrewAIPlugin, CrewAIService},
openai::{OpenAIPlugin, OpenAIService},
agno::{AgnoPlugin, AgnoService},
llamaindex::{LlamaIndexPlugin, LlamaIndexService},
smolagents::{SmolagentsPlugin, SmolagentsService},
base::PluginConfig,
};
// LangGraph — HTTP bridge to a LangServe endpoint
let service = LangGraphService { base_url: "http://localhost:8000".into(), ..Default::default() };
let agent = LangGraphPlugin::new().wrap(service, PluginConfig {
agent_id: "inai://agent/researcher".into(), owner: "0xYourWallet".into(), ..Default::default()
});
// OpenAI-compatible API (OpenAI, vLLM, Ollama, …)
let service = OpenAIService {
base_url: "https://api.openai.com".into(),
model: "gpt-4o-mini".into(),
api_key: Some(std::env::var("OPENAI_API_KEY").unwrap()),
..Default::default()
};
let agent = OpenAIPlugin::new().wrap(service, PluginConfig { .. });
// Agno — HTTP bridge to a deployed Agno FastAPI server
let service = AgnoService { base_url: "http://localhost:7777".into(), ..Default::default() };
let agent = AgnoPlugin::new().wrap(service, PluginConfig { .. });
// LlamaIndex — HTTP bridge to a LlamaIndex server
let service = LlamaIndexService { base_url: "http://localhost:8080".into(), ..Default::default() };
let agent = LlamaIndexPlugin::new().wrap(service, PluginConfig { .. });
// smolagents — Gradio or custom API bridge
let service = SmolagentsService { base_url: "http://localhost:7860".into(), ..Default::default() };
let agent = SmolagentsPlugin::new().wrap(service, PluginConfig { .. });
// Hermes (Nous Research) — embed via Python `wrap_hermes()` / `inai create ... --framework hermes` (no Rust SDK)
// CrewAI — HTTP bridge to a FastAPI-wrapped crew
let mut service = CrewAIService { base_url: "http://localhost:8000".into(), ..Default::default() };
let plugin = CrewAIPlugin::new();
plugin.fetch_capabilities(&mut service).await?;
let agent = plugin.wrap(service, PluginConfig { .. });
const lg = @import("plugins/langgraph.zig");
const adk = @import("plugins/google_adk.zig");
const ca = @import("plugins/crewai.zig");
const oai = @import("plugins/openai.zig");
const agno = @import("plugins/agno.zig");
const lli = @import("plugins/llamaindex.zig");
const sma = @import("plugins/smolagents.zig");
const Wrapped = @import("plugins/wrapped_agent.zig").WrappedAgent;
// LangGraph — HTTP bridge to a LangServe endpoint
var lg_service = lg.LangGraphService{ .base_url = "http://localhost:8000" };
var lg_plugin = lg.LangGraphPlugin.init(allocator);
defer lg_plugin.deinit();
var agent = Wrapped(lg.LangGraphService, lg.LangGraphPlugin).init(
&lg_service, &lg_plugin, .{ .agent_id = "inai://agent/researcher", .owner = "0x..." }, allocator,
);
// OpenAI-compatible API
var oai_service = oai.OpenAIService{ .base_url = "https://api.openai.com", .api_key = "sk-..." };
var oai_plugin = oai.OpenAIPlugin.init(allocator);
defer oai_plugin.deinit();
// Agno — deployed Agno server
var agno_service = agno.AgnoService{ .base_url = "http://localhost:7777" };
var agno_plugin = agno.AgnoPlugin.init(allocator);
defer agno_plugin.deinit();
// LlamaIndex — deployed LlamaIndex server
var lli_service = lli.LlamaIndexService{ .base_url = "http://localhost:8080" };
// smolagents — Gradio or custom API
var sma_service = sma.SmolagentsService{ .base_url = "http://localhost:7860" };
// Hermes (Nous Research) — no Zig module; use a Python agent with `wrap_hermes()`
Inai has a two-way bridge with the Model Context Protocol:
# Any MCP server → Inai agent (GitHub, filesystem, Slack, databases…)
from plugins.mcp_plugin import MCPPlugin
plugin = await MCPPlugin.from_command(
["npx", "-y", "@modelcontextprotocol/server-github"],
config, env={"GITHUB_TOKEN": "ghp_..."}
)
agent = plugin.wrap()
await agent.serve(port=8081)
# Any Inai agent → MCP server (Claude Desktop, Cursor, Continue…)
from adapters.mcp_server import serve_as_mcp
await serve_as_mcp(my_agent) # stdio (Claude Desktop)
await serve_as_mcp(my_agent, transport="sse", port=3000) # SSE (remote)
// TypeScript — same bridge
import { MCPPlugin } from './plugins/MCPPlugin';
import { serveAsMcp } from './adapters/MCPServer';
const plugin = await MCPPlugin.fromCommand(['npx', '-y', '@modelcontextprotocol/server-github'], config);
await plugin.wrap().serve({ port: 8081 });
await serveAsMcp(myAgent); // stdio
await serveAsMcp(myAgent, { transport: 'sse', port: 3000 });
// Rust — stdio subprocess or HTTP endpoint
use inai::mcp::{McpPlugin, serve_as_mcp, ServeMcpOptions, Transport};
use inai::plugins::base::PluginConfig;
// Wrap an MCP server (subprocess) → Inai agent
let agent = McpPlugin::from_command(
&["npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
PluginConfig { agent_id: "inai://agent/fs".into(), .. },
None,
).await?;
inai::server::serve(agent, 8081).await?;
// Expose a Inai agent → MCP server (stdio for Claude Desktop)
serve_as_mcp(my_agent, ServeMcpOptions::default()).await?;
// Expose over SSE (remote clients)
serve_as_mcp(my_agent, ServeMcpOptions {
transport: Transport::Sse, port: 3000, ..Default::default()
}).await?;
// Zig — stdio subprocess or HTTP endpoint
const mcp_plugin = @import("mcp_plugin.zig");
const mcp_server = @import("mcp_server.zig");
// Wrap an MCP server (subprocess) → Inai agent
var plugin = mcp_plugin.McpPlugin.initStdio(allocator);
defer plugin.deinit();
try plugin.fromCommand(&.{ "npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp" }, null);
// Expose a Inai agent → MCP server (stdio for Claude Desktop)
try mcp_server.serveAsMcp(MyAgent, &my_agent, .{}, allocator);
// Expose over HTTP (POST /mcp)
try mcp_server.serveAsMcp(MyAgent, &my_agent, .{ .transport = .http, .port = 3000 }, allocator);
→ Full guide: docs/mcp.md
from interfaces.iagent_client import AgentClient
from discovery.local_discovery import LocalDiscovery
client = AgentClient(discovery=LocalDiscovery.get_instance())
# 1. Find an agent by capability
entry = await client.find("web_search")
# 2. Connect (handshake: heartbeat + capability exchange) → AgentSession
session = await client.connect(entry)
print(session.capabilities) # ['web_search', 'summarise']
print(session.is_healthy) # True
# 3. Call via the session
resp = await session.call("web_search", {"query": "latest AI news"})
print(resp.result["content"])
# Or skip the handshake for one-off calls
resp = await client.call_capability("web_search", {"query": "latest AI news"})
→ Full guide: docs/interfaces.md
Inai includes a full DIDComm v2 implementation for end-to-end encrypted, authenticated messages between agents. No external key infrastructure required — each agent's did:key is derived from its identity keypair.
Crypto stack: X25519 ECDH key agreement + ChaCha20-Poly1305 AEAD.
Wire format: JWE JSON serialization with per-recipient key wrapping.
Modes: authcrypt (sender-authenticated) and anoncrypt (anonymous).
import { DidcommClient, MessageTypes } from './didcomm';
const alice = DidcommClient.generateKeyPair();
const bob = DidcommClient.generateKeyPair();
const aliceClient = new DidcommClient(alice);
const bobClient = new DidcommClient(bob);
// Alice encrypts an INVOKE message to Bob (authcrypt)
const encrypted = await aliceClient.invoke(bob.did, 'translate', { text: 'hello' });
// Bob decrypts it
const { message, senderDid } = await bobClient.unpack(encrypted);
console.log(message.body); // { text: 'hello' }
console.log(senderDid); // alice's did:key
use crate::didcomm::DidcommClient;
let alice = DidcommClient::generate()?;
let bob = DidcommClient::generate()?;
// Encrypt (authcrypt)
let packed = alice.invoke(&bob.did, "translate", json!({"text": "hello"}), false)?;
// Decrypt
let (msg, sender_did) = bob.unpack(&packed)?;
println!("{}", msg.body); // {"text":"hello"}
println!("{:?}", sender_did); // Some("did:key:z6Mk...")
// Anonymous — sender is not revealed
let anon = alice.invoke(&bob.did, "translate", json!({"text": "hi"}), true)?;
let (msg2, none_sender) = bob.unpack(&anon)?;
assert!(none_sender.is_none());
const didcomm = @import("didcomm.zig");
var alice = try didcomm.DidcommClient.generate(allocator);
defer alice.deinit();
var bob = try didcomm.DidcommClient.generate(allocator);
defer bob.deinit();
// Encrypt
const packed = try alice.invoke(allocator, bob.did, "translate", "{\"text\":\"hello\"}", false);
defer allocator.free(packed);
// Decrypt
const result = try bob.unpack(allocator, packed);
defer result.deinit(allocator);
std.debug.print("body: {s}\n", .{result.message.body_json});
std.debug.print("from: {?s}\n", .{result.sender_did});
Source: templates/typescript/didcomm.ts · templates/rust/src/didcomm.rs · templates/zig/src/didcomm.zig
| Layer | Default address | Env override |
|---|---|---|
HTTP server (/invoke, /health, …) |
0.0.0.0:6174 |
INAI_PORT |
| libp2p TCP (GossipSub + request-response) | /ip4/0.0.0.0/tcp/6174 |
INAI_P2P_ADDR |
| libp2p QUIC (Rust/Zig DHT) | /ip4/0.0.0.0/udp/6174/quic-v1 |
INAI_P2P_PORT |
| Bootstrap peers | (none — mDNS only on LAN) | INAI_BOOTSTRAP_PEERS (comma-separated multiaddrs) |
All discovery traffic — DHT announces, gossip fan-out, and capability queries — travels over the libp2p transport on the same port. There is no separate discovery port.
Every agent's ANR always carries the full multiaddr when running in libp2p mode:
/ip4/<host>/tcp/<port>/p2p/<PeerId> # TCP (TypeScript)
/ip4/<host>/udp/<port>/quic-v1/p2p/<PeerId> # QUIC (Rust)
When only HTTP transport is active, network.multiaddr is empty and network.protocol is "http".
Every agent has a DID derived from its secp256k1 keypair — no wallet or gas required.
| Mode | DID format | agentId |
|---|---|---|
local (default) |
did:key:zQ3sh… |
inai://agent/<eth-addr> |
env |
did:key:zQ3sh… |
same, driven by INAI_AGENT_KEY |
erc8004 |
did:pkh:eip155:<chainId>:0x… |
on-chain verified owner |
The DID is a multicodec-prefixed, base58btc-encoded secp256k1 compressed public key:
did:key:z <base58btc( 0xe701 || compressed-secp256k1-pubkey )>
↑ secp256k1 multicodec varint
The ANR is the canonical self-description of an agent. It is returned by GET /anr, stored in the Kademlia DHT, and broadcast via gossip. Its shape is DiscoveryEntry:
interface DiscoveryEntry {
agentId: string; // "inai://agent/0xABC…" or a DID
name: string; // human-readable label
owner: string; // Ethereum address or DID of the keyholder
capabilities: string[]; // ["echo", "web_search", "generate_image"]
network: {
protocol: 'http' | 'websocket' | 'grpc' | 'tcp' | 'libp2p';
host: string; // "192.168.1.5" or "agent.example.com"
port: number;
tls: boolean;
peerId?: string; // libp2p PeerId (when protocol === 'libp2p')
multiaddr?: string; // full multiaddr, e.g. "/ip4/…/udp/…/quic-v1/p2p/…"
};
health: {
status: 'healthy' | 'degraded' | 'unhealthy';
lastHeartbeat: string; // ISO 8601
uptimeSeconds: number;
};
registeredAt: string; // ISO 8601
metadataUri?: string; // IPFS / HTTPS link to extended metadata
}
The DHT stores a signed envelope around this record:
{
"v": 1,
"seq": 42,
"entry": { /* StoredEntry fields */ },
"sig": "<base64-compact-secp256k1-signature-over-sha256(unsigned-envelope)>"
}
Capabilities are plain strings declared by getCapabilities(). They form the unit of discovery and billing:
getCapabilities(): string[] // e.g. ["echo", "web_search", "generate_image"]
For plugin-wrapped agents a CapabilityDescriptor carries richer metadata:
interface CapabilityDescriptor {
name: string;
description: string;
inputSchema?: Record<string, unknown>; // JSON Schema
outputSchema?: Record<string, unknown>; // JSON Schema
pricePerCall?: string; // "0.05 USDC" — triggers x402 gate
}
When pricePerCall is set the HTTP server automatically returns HTTP 402 on calls that carry no payment proof.
POST /invoke — AgentRequest / AgentResponseRequest
interface AgentRequest {
requestId: string; // UUID v4
from: string; // caller agentId or wallet address
capability: string; // target capability name
payload: Record<string, unknown>; // capability-specific body
signature?: string; // EIP-712 signature over the envelope
timestamp?: number; // Unix ms — used to reject stale calls
sessionKey?: string; // delegated execution session
payment?: { type, token, amount, txHash? }; // legacy payment field
x402?: X402Payment; // x402 micropayment proof (auto-attached by X402Client)
stream?: boolean; // true → use POST /invoke/stream (SSE)
}
Response
interface AgentResponse {
requestId: string;
status: 'success' | 'error' | 'payment_required';
result?: Record<string, unknown>; // present on success
errorMessage?: string; // present on error / payment_required
proof?: string; // optional ZK proof or attestation
signature?: string; // EIP-712 response signature
timestamp?: number; // Unix ms
paymentRequirements?: X402PaymentRequirements[]; // present on payment_required
}
Wire example
// POST /invoke
{ "requestId": "a1b2", "from": "inai://agent/caller", "capability": "web_search",
"payload": { "query": "latest AI news" }, "timestamp": 1711234567000 }
// 200 OK
{ "requestId": "a1b2", "status": "success",
"result": { "content": "…", "sources": ["…"] }, "timestamp": 1711234567120 }
// 402 Payment Required
{ "requestId": "a1b2", "status": "payment_required",
"errorMessage": "Capability 'generate_image' requires payment.",
"paymentRequirements": [{ "network": "base", "asset": "0x833…", "maxAmountRequired": "50000", "payTo": "0xYour…" }] }
These are dispatched via the same POST /invoke endpoint using reserved capability names.
__heartbeat// AgentRequest.capability = "__heartbeat"
// AgentRequest.payload cast to:
interface HeartbeatRequest {
senderId: string;
timestamp: number; // Unix ms
nonce?: string;
}
// AgentResponse.result cast to:
interface HeartbeatResponse {
agentId: string;
status: 'healthy' | 'degraded' | 'unhealthy';
timestamp: number;
capabilitiesCount: number;
uptimeMs?: number;
version?: string;
nonce?: string; // echoed from request
}
__capabilities// AgentRequest.payload cast to:
interface CapabilityExchangeRequest {
senderId: string;
timestamp: number;
includeAnr: boolean; // true → response includes full DiscoveryEntry
}
// AgentResponse.result cast to:
interface CapabilityExchangeResponse {
agentId: string;
capabilities: string[];
timestamp: number;
anr?: DiscoveryEntry; // present when includeAnr was true
}
POST /gossipGossip is fire-and-forget — the server always returns { "ok": true }. Messages propagate hop by hop; each hop decrements ttl and appends its own ID to seenBy to prevent loops.
interface GossipMessage {
type: 'announce' | 'revoke' | 'heartbeat' | 'query';
senderId: string;
timestamp: number; // Unix ms
ttl: number; // decremented each hop; dropped at 0
seenBy: string[]; // agent IDs that have already forwarded this
entry?: DiscoveryEntry; // present for announce / revoke
capability?: string; // present for query
nonce?: string;
}
Wire example
// POST /gossip
{ "type": "announce", "senderId": "inai://agent/0xABC",
"timestamp": 1711234567000, "ttl": 3, "seenBy": [],
"entry": { "agentId": "inai://agent/0xABC", "capabilities": ["web_search"], … } }
// 200 OK
{ "ok": true }
POST /invoke/streamSet AgentRequest.stream = true (or call POST /invoke/stream directly). The server responds with Content-Type: text/event-stream and emits SSE frames until the terminal StreamEnd frame.
// Each incremental frame:
interface StreamChunk {
requestId: string;
type: 'chunk';
delta: string; // LLM token text or incremental output
result?: unknown; // optional partial structured result
sequence: number; // monotonically increasing per request
timestamp: number;
}
// Terminal frame:
interface StreamEnd {
requestId: string;
type: 'end';
finalResult?: unknown; // fully assembled result
error?: string; // set on abnormal termination
sequence: number;
timestamp: number;
}
Wire example
data: {"type":"chunk","requestId":"a1b2","delta":"The ","sequence":1,"timestamp":1711234567100}
data: {"type":"chunk","requestId":"a1b2","delta":"latest ","sequence":2,"timestamp":1711234567110}
data: {"type":"end","requestId":"a1b2","finalResult":{"text":"The latest AI news…"},"sequence":47,"timestamp":1711234567890}
| Adapter | Backend | Use case |
|---|---|---|
LocalDiscovery |
In-memory | Dev & testing |
HttpDiscovery |
REST API | Centralised staging |
GossipDiscovery |
HTTP fan-out + TTL | Decentralised mesh (no DHT required) |
Libp2pDiscovery |
P2P / Kademlia DHT | Production mesh |
OnChainDiscovery |
ERC-8004 smart contract | On-chain registry (optional) |
| Mode | DID format | How |
|---|---|---|
local (default) |
did:key:z... |
Key auto-created in ~/.inai/keystore/ |
env |
did:key:z... |
INAI_AGENT_KEY=0x... env var |
raw |
did:key:z... |
Pass key directly (secret manager, HSM) |
erc8004 (optional) |
did:pkh:eip155:<chainId>:0x... |
On-chain wallet — adds verifiable ownership |
from identity.provider import LocalKeystoreIdentity
identity = LocalKeystoreIdentity(name="my-agent")
print(identity.agent_id()) # did:key:zQ3shXXX...
config = PluginConfig(**identity.to_plugin_config_fields(), port=6174)
→ Full guide: docs/identity.md
Charge other agents per capability in USDC on Base. Agents without pricing serve all requests free.
from addons.x402.types import CapabilityPricing
config = PluginConfig(
...
x402_pricing={
"generate_image": CapabilityPricing.usdc_base(0.05, "0xYourWallet"), # $0.05 per call
}
)
The HTTP server gate is automatic — no code needed in the agent. Callers receive an HTTP 402 with the full payment challenge if they haven't included a proof.
→ Full guide: docs/x402.md
examples/cross-framework/ is a working end-to-end demo you can run right now:
git clone https://github.com/ch4r10t33r/inai
python3 examples/cross-framework/run.py
A ResearchAgent (Google ADK) and a WriterAgent (CrewAI) register with LocalDiscovery, then the orchestrator uses AgentClient to discover and call them in sequence — research findings flow from ADK into CrewAI without either agent knowing the other's framework. Runs in demo mode by default (no API keys needed); set GOOGLE_API_KEY and OPENAI_API_KEY to enable real LLMs.
A Google ADK research agent and a CrewAI writer agent discovering and calling each other with zero framework coupling:
cd examples/cross-framework
pip install -r requirements.txt
python run_example.py
| Module | Description | Status |
|---|---|---|
| AMP-1 | Discovery (capability indexing + queries) | ✅ Stable |
| AMP-2 | Interaction (request/response + routing) | ✅ Stable |
| AMP-3 | Payments (x402 micropayments) | ✅ Add-on available |
| AMP-4 | Delegation & multi-agent workflows | 🔜 Roadmap |
| Doc | Description |
|---|---|
| docs/overview.md | Architecture and core concepts |
| docs/interfaces.md | IAgent, AgentSession, IAgentClient — full interface reference |
| docs/identity.md | DID identity — all modes, ERC-8004 optional |
| docs/x402.md | x402 payment add-on |
| docs/mcp.md | MCP bridge — wrap MCP servers, expose as MCP server |
| docs/discovery.md | Discovery adapters |
| docs/libp2p.md | P2P networking with libp2p + QUIC |
| docs/plugins.md | Framework adapters — LangGraph, Google ADK, CrewAI, OpenAI, Agno, LlamaIndex, smolagents, Hermes, MCP |
| docs/differentiation.md | How Inai differs from other frameworks |
| docs/vs-a2a.md | Inai vs A2A — detailed technical comparison |
A2A defines how two agents talk. Inai defines how agents find each other, prove who they are, and transact — problems A2A explicitly leaves out of scope.
| A2A | Inai | |
|---|---|---|
| Discovery | You need the agent's URL; bring your own registry | Kademlia DHT — find any agent by capability, no URL needed |
| Identity | Self-declared Agent Card, no cryptographic verification | did:key — keypair-derived, portable, verifiable |
| Encryption | TLS (transport only) | DIDComm v2 — end-to-end, message-level |
| Payments | Out of scope | x402 micropayments built in |
| Network | Requires public HTTPS endpoint | P2P via libp2p QUIC; circuit relay for agents behind NAT |
| Routing | Call a specific URL | Query by capability; mesh returns candidates |
| Task model | Rich state machine (8 states, artifacts, webhooks) | Simple /invoke + /invoke/stream |
| Enterprise auth | OAuth2, OIDC, mTLS — first-class | Via HTTP layer |
They are complementary, not competing. A2A handles the task conversation; Inai handles discovery, identity, encryption, and payment. A Inai agent can expose an A2A-compatible endpoint — discovered via Inai's DHT and invoked using A2A's task protocol.
→ Full analysis: docs/vs-a2a.md
checkPermission) on-chain enforcementstreamRequest method)Apache 2.0 — see LICENSE
Connect AI agents across any network — zero config, encrypted, skill-based routing
Golang SDK for A2A Protocol
A curated list of Agent-to-Agent (A2A) libraries and SDKs, organized by programming language.
Demo agents showcasing CapiscIO Agent Guard and MCP Guard — trust badges, identity verification, and tool-level authorization for A2A and MCP protocols
Elixir implementation of the Agent-to-Agent (A2A) protocol (https://a2a-protocol.org).
kbs_agent_a2a 智能体协议适配中间件
Agent behavior that compiles
An open SDK for agentic payments. Let AI agents make payments, hold funds, and move money across chains with policy enforcement and human approval built in.
x402 payments in Rust: verify, settle, and monitor payments over HTTP 402 flows
The self-improving LLM router that optimize your agentic workflows with every runs, works with any harnesses, any models, any loops.
Production-ready x402 facilitator server.
Rust SDK for the x402 payment protocol.