Write your agent in any framework. Wrap it with
bindufy(). Ship a signed A2A microservice — identity, OAuth2, and on-chain payments — in ten lines of code.
No infrastructure to write. No framework to rewrite. Works from Python, TypeScript, and Kotlin, and layered on two open protocols: A2A and x402.
When you wrap a handler with bindufy(config, handler), the process comes up speaking standard protocols, signing every response, and ready to take payment. Grouped by what it does for you:
Protocol — talk to the world
| Capability | What it means |
|---|---|
| A2A JSON-RPC endpoint | Standard protocol other agents already speak. message/send, tasks/get, message/stream on port 3773. |
| Push notifications | Webhook callbacks on task state change — no polling required. |
| Language-agnostic | Python, TypeScript, and Kotlin SDKs share one gRPC core. Same protocol, same DID, same auth. |
Identity & access — prove who's calling
| Capability | What it means |
|---|---|
| DID identity (Ed25519) | Every returned artifact is signed. Callers verify with a W3C-standard DID — no shared secrets. |
| OAuth2 via Ory Hydra | Scoped tokens (agent:read, agent:write, agent:execute) instead of one all-or-nothing bearer. |
Commerce & reach — get paid and be reachable
| Capability | What it means |
|---|---|
| x402 payments | One flag and the agent charges USDC on Base before processing a request. Payment check runs before your handler. |
| Public tunnel | expose: true opens an FRP tunnel so your local agent is reachable from the public internet. |
uv add binduFor a development checkout with tests:
git clone https://github.com/getbindu/Bindu.git
cd Bindu
uv sync --devRequires Python 3.12+ and uv. An API key for at least one LLM provider (OPENROUTER_API_KEY, OPENAI_API_KEY, or MINIMAX_API_KEY) is needed to run the examples.
The whole idea of Bindu shows up clearly in one file — build any agent you like, hand it to bindufy(), and your process comes up as a signed A2A microservice. The block below is complete and runnable.
import os
from bindu.penguin.bindufy import bindufy
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.duckduckgo import DuckDuckGoTools
# 1. Build your agent with whatever framework you prefer. Bindu doesn't
# care what's inside — it just needs something callable.
agent = Agent(
instructions="You are a research assistant that finds and summarizes information.",
model=OpenAIChat(id="gpt-4o"),
tools=[DuckDuckGoTools()],
)
# 2. Tell Bindu who you are and where the agent lives. `expose: True`
# opens a public FRP tunnel — drop it for local-only.
config = {
"author": "you@example.com",
"name": "research_agent",
"description": "Research assistant with web search.",
"deployment": {
"url": os.getenv("BINDU_DEPLOYMENT_URL", "http://localhost:3773"),
"expose": True,
},
"skills": ["skills/question-answering"],
}
# 3. The handler contract: (messages) -> response. That's it.
def handler(messages: list[dict[str, str]]):
return agent.run(input=messages)
# 4. bindufy() boots the HTTP server, mints your DID, registers with
# Hydra (if auth is on), and starts accepting A2A calls.
bindufy(config, handler)Run it, and the agent is live at the configured URL. Need a different port? Export BINDU_PORT=4000 — no code change.
TypeScript equivalent
import { bindufy } from "@bindu/sdk";
import OpenAI from "openai";
const openai = new OpenAI();
bindufy({
author: "you@example.com",
name: "research_agent",
description: "Research assistant.",
deployment: { url: "http://localhost:3773", expose: true },
skills: ["skills/question-answering"],
}, async (messages) => {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: messages.map(m => ({ role: m.role as "user" | "assistant" | "system", content: m.content })),
});
return response.choices[0].message.content || "";
});The TypeScript SDK launches the Python core automatically. Same protocol, same DID. Full example in examples/typescript-openai-agent/.
Calling the agent with curl
curl -X POST http://localhost:3773/ \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"method": "message/send",
"id": "<uuid>",
"params": {
"message": {
"role": "user",
"kind": "message",
"parts": [{"kind": "text", "text": "Hello"}],
"messageId": "<uuid>",
"contextId": "<uuid>",
"taskId": "<uuid>"
}
}
}'Poll tasks/get with the same taskId until state is completed. The returned artifact carries a DID signature under metadata["did.message.signature"].
So what actually happens when that bindufy() call executes? The handler is the only code you write. Everything else is scaffolding Bindu puts around it:
your handler ──► bindufy(config, handler)
│
▼
┌────────────────────────────────────┐
│ Bindu core (HTTP :3773) │
│ OAuth2 (Hydra) │
│ DID verification │
│ x402 payment check (optional) │
│ Task manager + scheduler │
└────────────────────────────────────┘
│
▼
A2A-signed artifact returned to caller
bindufy() is a thin wrapper. Your handler stays pure — (messages) -> response. Bindu owns identity, protocol, auth, payment, storage, and scheduling.
TL;DR — when
AUTH__ENABLED=true, every call needs a Hydra bearer token and threeX-DID-*headers. Python client: ~25 lines, below. Postman: paste one script. The rest of this section unpacks why and how, and what goes wrong when it goes wrong.
The curl example in Hello agent works because auth is off by default — anyone can POST to your agent. The moment you flip AUTH__ENABLED=true AUTH__PROVIDER=hydra, your agent gets stricter. Every caller now has to answer two questions before the handler runs:
- Are you allowed to call me? — show a valid OAuth2 token from Hydra.
- Are you really who you say you are? — sign the request with a DID key.
Think of it like boarding a flight: the boarding pass (OAuth token) says "yes, you have a seat on this flight," and the passport (DID signature) says "and you really are the person on that boarding pass." The server checks both.
The full theory lives in docs/AUTHENTICATION.md and docs/DID.md — plain-English, no crypto background assumed. What follows is the practical "I just want to call my agent" version.
Alongside the usual Authorization: Bearer <hydra-jwt>, every secured request carries:
| Header | Value |
|---|---|
X-DID |
your DID string, e.g. did:bindu:you_at_example_com:myagent:<uuid> |
X-DID-Timestamp |
current unix seconds (server allows 5 min skew) |
X-DID-Signature |
base58( Ed25519_sign( <signing payload> ) ) |
The signing payload is reconstructed on the server like this:
json.dumps({"body": <raw-body-string>, "did": <did>, "timestamp": <ts>}, sort_keys=True)Two gotchas that will bite you until you've felt them:
- Match Python's JSON spacing. Python's default
json.dumpswrites", "and": "(with spaces).JSON.stringifyin JS writes them without. If your payload serializes differently, Ed25519 sees different bytes and the server returnsreason="crypto_mismatch". - Sign what you send. If you parse the body, modify it, re-serialize, and ship that — you signed the wrong bytes. Build the body string once, sign those exact bytes, send those exact bytes.
The agent prints a ready-to-run curl in its startup banner. The short version:
SECRET=$(jq -r '.[].client_secret' < .bindu/oauth_credentials.json)
curl -X POST https://hydra.getbindu.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=did:bindu:you_at_example_com:myagent:<uuid>" \
-d "client_secret=$SECRET" \
-d "scope=openid offline agent:read agent:write"The response has an access_token. It's good for about an hour — cache it, refetch when you need it.
Python — the shortest working example. Reads the agent's own keys (Bindu writes them to .bindu/ on first boot), signs a request, polls for the result. Self-call works because the agent's keys are a valid caller identity.
import base58, httpx, json, time, uuid
from pathlib import Path
from cryptography.hazmat.primitives import serialization
# 1. Load the keys Bindu wrote on first boot
priv = serialization.load_pem_private_key(Path(".bindu/private.pem").read_bytes(), password=None)
creds = next(iter(json.loads(Path(".bindu/oauth_credentials.json").read_text()).values()))
did = creds["client_id"] # DID doubles as the Hydra client_id
# 2. Exchange credentials for a short-lived JWT
bearer = httpx.post("https://hydra.getbindu.com/oauth2/token", data={
"grant_type": "client_credentials",
"client_id": creds["client_id"], "client_secret": creds["client_secret"],
"scope": "openid offline agent:read agent:write",
}).json()["access_token"]
# 3. Build the body ONCE — these are the bytes we'll sign AND send
tid = str(uuid.uuid4())
body = json.dumps({
"jsonrpc": "2.0", "method": "message/send", "id": str(uuid.uuid4()),
"params": {"message": {
"role": "user", "kind": "message",
"parts": [{"kind": "text", "text": "Hello!"}],
"messageId": str(uuid.uuid4()), "contextId": str(uuid.uuid4()), "taskId": tid,
}},
})
# 4. Sign: base58(Ed25519( json.dumps({body,did,timestamp}, sort_keys=True) ))
ts = int(time.time())
payload = json.dumps({"body": body, "did": did, "timestamp": ts}, sort_keys=True)
sig = base58.b58encode(priv.sign(payload.encode())).decode()
# 5. Fire it
r = httpx.post("http://localhost:3773/", content=body, headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {bearer}",
"X-DID": did,
"X-DID-Timestamp": str(ts),
"X-DID-Signature": sig,
})
print(r.status_code, r.json())For a full-featured version with polling and error handling, see examples/hermes_agent/call.py.
Postman — paste one script into your collection.
- Open your collection → Pre-request Script tab → paste the contents of
docs/postman-did-signing.js. - Set two collection variables:
bindu_did(your DID string) andbindu_did_seed(your 32-byte Ed25519 seed, base64-encoded). - Add an
Authorization: Bearer {{bindu_bearer}}header and drop your Hydra token intobindu_bearer. - Hit Send. The script signs the exact body bytes Postman is about to send and sets the three
X-DID-*headers for you.
Requires Postman Desktop v11+ (needs Ed25519 in crypto.subtle).
Plain curl — technically possible, usually painful. The signature depends on the body bytes you're about to send, so you need a helper script to compute the signature first, then substitute it into the curl call. If you're doing this, you're probably better off using the Python client above.
The server logs one of three reasons. If your request gets rejected with a 403, ask the operator (or check the server log yourself):
| Log says | What it means | Fix |
|---|---|---|
timestamp_out_of_window |
Your X-DID-Timestamp is more than 5 min off the server's clock, or you reused an old timestamp |
Recompute int(time.time()) on every request |
malformed_input |
The base58 decoding of the signature or public key failed | Check the X-DID-Signature isn't URL-encoded, truncated, or wrapped in quotes |
crypto_mismatch |
The bytes you signed ≠ the bytes you sent | Rebuild the payload with sort_keys=True and Python's default JSON spacing; sign the raw body string once and send the same bytes |
One sharper failure mode we hit in testing: if crypto_mismatch persists and you're sure your bytes match, Hydra's stored public key for this DID may be stale from an older registration. Fix: stop the agent, delete .bindu/oauth_credentials.json, restart — Hydra's client record will be refreshed with the current keys.
A single bindufy()-wrapped agent is a microservice. The Bindu Gateway is a task-first orchestrator that sits on top: give it a user question and a catalog of A2A agents, and a planner LLM decomposes the work, calls the right agents over A2A, and streams results back as Server-Sent Events. No DAG engine, no separate orchestrator service — the planner's LLM picks tools per turn.
What you get beyond a single agent:
- One endpoint:
POST /plan— hand it a question and an agent catalog, get streamed steps. - Agent catalog per request — external systems pass the list of agents, skills, and endpoints. No fleet hosting in the gateway itself.
- Session persistence (Supabase) — Postgres-backed with compaction, revert, and multi-turn history.
- Native TypeScript A2A — no Python subprocess, no
@bindu/sdkdependency in the gateway. - Optional DID signing + Hydra integration — gateway identity end-to-end.
Minimal quickstart:
cd gateway
npm install
cp .env.example .env.local # fill SUPABASE_*, GATEWAY_API_KEY, OPENROUTER_API_KEY
npm run dev # → http://localhost:3774
curl -sS http://localhost:3774/healthApply the two Supabase migrations first (gateway/migrations/001_init.sql, 002_compaction_revert.sql). Full walkthrough and operator reference live in gateway/README.md and docs/GATEWAY.md (45-minute end-to-end: clean clone → three chained agents → authoring a recipe → DID signing).
Gateway documentation:
| Topic | Link |
|---|---|
| Overview | docs.getbindu.com/bindu/gateway/overview |
| Quickstart | docs.getbindu.com/bindu/gateway/quickstart |
| Multi-agent planning | docs.getbindu.com/bindu/gateway/multi-agent |
| Recipes (progressive-disclosure playbooks) | docs.getbindu.com/bindu/gateway/recipes |
| Identity (DID signing, Hydra) | docs.getbindu.com/bindu/gateway/identity |
| Production deployment | docs.getbindu.com/bindu/gateway/production |
| API reference | docs.getbindu.com/api/introduction |
For a runnable multi-agent demo, see examples/gateway_test_fleet/ — five small agents on local ports, one gateway, one query.
Bring whichever agent framework you already like. You hand Bindu a handler; it gives you a signed A2A microservice. Same flow regardless of what's inside the handler.
| Language | Frameworks tested in this repo |
|---|---|
| Python | AG2 · Agno · CrewAI · Hermes Agent · LangChain · LangGraph · Notte |
| TypeScript | OpenAI SDK · LangChain.js |
| Kotlin | OpenAI Kotlin SDK |
| Any other language | via the gRPC core — add an SDK in a few hundred lines |
Compatible with any LLM provider that speaks the OpenAI or Anthropic API: OpenRouter (100+ models), OpenAI, MiniMax, and others.
Five that cover the spectrum of what Bindu can do. All 20+ runnable examples live under examples/.
| Example | What it shows |
|---|---|
| Agent Swarm | Multi-agent collaboration — a small "society" of Agno agents delegating work to each other. |
| Premium Advisor | x402 payments — caller has to pay USDC on Base before the handler runs. |
| Hermes via Bindu | Third-party framework interop — Nous Research's Hermes agent bindufied in ~90 lines. |
| Gateway Test Fleet | Five small agents + one gateway — the multi-agent orchestration story end-to-end. |
| TypeScript OpenAI Agent | Polyglot proof — a TS agent bindufied with the Bindu TS SDK; no Python to write. |
See the full catalog: examples/ — 20+ agents covering CSV analysis, PDF Q&A, speech-to-text, web scraping, cybersecurity newsletters, multi-lingual collab, blog writing, and more.
Missing a framework you use? Open an issue or ask on Discord.
A built-in chat UI is available at http://localhost:5173 after running cd frontend && npm run dev.
Everything below is optional and modular — the minimal install is just the A2A server. Each row links to a dedicated guide in docs/.
Identity & access
| Feature | Guide |
|---|---|
| Decentralized Identifiers (DIDs) | DID.md |
| Authentication (Ory Hydra OAuth2) | AUTHENTICATION.md |
Protocol & infrastructure
| Feature | Guide |
|---|---|
| Skills system | SKILLS.md |
| Agent negotiation | NEGOTIATION.md |
| Push notifications | NOTIFICATIONS.md |
| PostgreSQL storage | STORAGE.md |
| Redis scheduler | SCHEDULER.md |
| Language-agnostic via gRPC | GRPC_LANGUAGE_AGNOSTIC.md |
Commerce & reach
| Feature | Guide |
|---|---|
| x402 payments (USDC on Base) | PAYMENT.md |
| Tunneling (local dev only) | TUNNELING.md |
Reliability & operations
| Feature | Guide |
|---|---|
| Retry with exponential backoff | Retry docs |
| Observability (OpenTelemetry, Sentry) | OBSERVABILITY.md |
| Health check and metrics | HEALTH_METRICS.md |
Bindu targets 70% test coverage (goal: 80%+):
uv run pytest tests/unit/ -v # fast unit tests
uv run pytest tests/integration/grpc/ -v -m e2e # gRPC E2E
uv run pytest -n auto --cov=bindu --cov-report=term-missing # full suiteCI runs unit tests, gRPC E2E, and TypeScript SDK build on every PR. See .github/workflows/ci.yml.
If you're running Bindu in production, read bugs/known-issues.md first. It's a per-subsystem catalog with workarounds. Postmortems for fixed bugs live under bugs/core/, bugs/gateway/, bugs/sdk/, and bugs/frontend/.
Current high-severity items:
| Subsystem | Slug | Symptom |
|---|---|---|
| Core | x402-middleware-fails-open-on-body-parse |
Malformed JSON body bypasses payment check |
| Core | x402-no-replay-prevention |
One payment buys unlimited work until validBefore |
| Core | x402-no-signature-verification |
EIP-3009 signature is never verified |
| Core | x402-balance-check-skipped-on-missing-contract-code |
Misconfigured RPC silently skips balance check |
| Gateway | context-window-hardcoded |
Compaction threshold assumes a 200k-token window |
| Gateway | poll-budget-unbounded-wall-clock |
sendAndPoll can stall 5 minutes per tool call |
| Gateway | no-session-concurrency-guard |
Two /plan calls on the same session tangle histories |
Found a new issue? Open a GitHub Issue referencing the slug (e.g. "Fixes context-window-hardcoded"). Fixed one? Remove its entry from known-issues.md and add a dated postmortem — see bugs/README.md for the template.
Common issues
| Issue | Fix |
|---|---|
uv: command not found |
Restart your shell after installing uv. |
Python version not supported |
Install Python 3.12+ from python.org or via pyenv. |
bindu: command not found |
Activate your virtualenv: source .venv/bin/activate. |
Port 3773 already in use |
Set BINDU_PORT=4000, or override with BINDU_DEPLOYMENT_URL=http://localhost:4000. |
ModuleNotFoundError |
Run uv sync --dev. |
| Pre-commit fails | Run pre-commit run --all-files. |
Permission denied (macOS) |
xattr -cr . to clear extended attributes. |
Reset the environment:
rm -rf .venv && uv venv --python 3.12.9 && uv sync --devOn Windows PowerShell you may need Set-ExecutionPolicy RemoteSigned -Scope CurrentUser.
Clone, set up, and run the pre-commit hooks:
git clone https://github.com/getbindu/Bindu.git
cd Bindu
uv venv --python 3.12.9 && source .venv/bin/activate
uv sync --dev
pre-commit run --all-filesDiscussion and help happen on Discord. See .github/contributing.md for the full guide. There's an open list of agents we'd like to see bindufied — contribute one.
Raahul Dutta |
Paras Chamoli |
Chandan |
Bindu stands on the shoulders of:
FastA2A · A2A · x402 · Hugging Face chat-ui · 12 Factor Agents · OpenCode · OpenMoji · ASCII Space Art
Apache 2.0. See LICENSE.md.
Crafted between Amsterdam and India · open source under Apache 2.0 · getbindu.com



