Getting Started
Neuronum is a data network enabling distributed AI agents to communicate securely across devices with built-in end-to-end encryption, identity, routing, and delivery by simple function calls.
You focus on building your agent's logic. Neuronum handles the rest.
⚠️ Development Status: The Neuronum SDK is currently in beta and is not production-ready. It is intended for development, testing, and experimental purposes only. Do not use in production environments or for critical applications.
Next Steps
Learn more about Neuronum and get ready to build?
- Cell - Your unique identity
- Agent - Build and deploy agents
- Methods - Full API reference
- Encryption - Secure data exchange
- Installation - Install the SDK
- Start Building - Build on Neuronum
- A2A Guide - Set up an A2A workflow
Need Help? For more information, visit the GitHub repository or contact us.
Installation
Requirements
- Python >= 3.8
Setup and activate a virtual environment
python3 -m venv ~/neuronum-venv
source ~/neuronum-venv/bin/activate
Install the Neuronum SDK
pip install neuronum
What's next? Create your Cell, check out the Methods, or learn about the E2EE Protocol.
Cell
A Cell is your address for sending and receiving data on the Neuronum network. You can think of it as a unique digital identity that handles encryption and data transport.
Create a Cell
neuronum create-cell
This generates your Cell ID, public/private key pair, and a 12-word mnemonic recovery phrase. See the E2EE section for details on key generation.
Your Cell credentials are stored locally at ~/.neuronum/.env
Connect a Cell
Connect an existing Cell to a new device using your 12-word mnemonic:
neuronum connect-cell
View Cell
View the Cell ID connected on this device:
neuronum view-cell
Disconnect Cell
Remove the Cell credentials from this device:
neuronum disconnect-cell
Delete Cell
Permanently delete your Cell from the Neuronum network:
neuronum delete-cell
Need Help? For more information, visit the GitHub repository or contact us.
Agent
An Agent is any (AI) service that you build upon your Cell, exposing skills that other agents/cells can discover and call. Each agent has its own configuration, handles, and logic.
Initialize a new Agent
neuronum init-agent
This creates an agent folder named agent_agent_id with agent.py, model.py, and agent.config.
agent.config
The agent configuration file (inspired by Google's A2A protocol Agent Card):
{
"agent_meta": {
"agent_id": "019d8671-22c8-7a91-9fa7-8eb46d85969b",
"version": "1.0.0",
"name": "Q&A Agent",
"description": "An agent that returns answers to natural language prompts",
"audience": "private",
"logo": "https://neuronum.net/static/logo_new.png"
},
"skills": [
{
"handle": "get_answer",
"description": "Ask a question and get an answer.",
"examples": [
"What is the capital of France?",
"Explain quantum mechanics simply."
],
"stream": false,
"input_schema": {
"properties": {
"query": {
"type": "string",
"description": "The user request"
},
"context": {
"type": "string",
"description": "Optional background information"
}
},
"required": [
"query"
]
}
}
],
"legals": {
"terms": "https://url_to_your/legals",
"privacy_policy": "https://url_to_your/legals"
}
}
Configuration Reference
- agent_meta.agent_id Auto-generated. Do not change
- agent_meta.version Version of your agent. Update as needed
- agent_meta.name Display name of your agent
- agent_meta.description What your agent does
- agent_meta.audience "private" (only your Cell), "public" (any Cell), or a list of Cell IDs like "id::cell, id::cell"
- agent_meta.logo URL to your agent's logo
- skills List of skills your agent exposes. Add, remove, or modify as needed
- skills[].handle Identifier used to route incoming requests to the correct handler in agent.py
- skills[].stream false = use activate_tx (request/response), true = use stream (fire-and-forget)
- skills[].description What the skill does
- skills[].examples Example prompts or inputs
- skills[].input_schema JSON Schema defining the expected input fields
- legals Links to your terms of service and privacy policy
Start your Agent
neuronum start-agent
Stop your Agent
neuronum stop-agent
Update your Agent
After changing an agent.config file:
neuronum update-agent
Delete your Agent
neuronum delete-agent
Need Help? For more information, visit the GitHub repository or contact us.
Methods
Cells interact using six methods to stream, request, receive, and respond to encrypted data across the network.
How it works
- list_cells() — List all Neuronum Cells
- list_agents() — List all Agents built on Neuronum
- stream(data, cell_id) — Send data to another Cell (fire-and-forget). Defaults to own Cell if no cell_id is provided
- activate_tx(data, cell_id) — Send a request and wait for a response. Defaults to own Cell if no cell_id is provided
- sync() — Listen for incoming transmissions
- tx_response(tx_id, data, public_key) — Send an encrypted response back to the requesting Cell
All data is end-to-end encrypted. The network handles routing, key exchange, and delivery. You just send and receive.
Connecting to the Network
All methods are called on a Cell instance. Use async with Cell() as cell to connect. This reads your Cell credentials from ~/.neuronum/.env and uses it to connect to the Neuronum network.
Quick Example
List Cells
import asyncio
from neuronum import Cell
async def main():
async with Cell() as cell:
cells = await cell.list_cells()
print(cells)
asyncio.run(main())
List Agents
import asyncio
from neuronum import Cell
async def main():
async with Cell() as cell:
agents = await cell.list_agents()
print(agents)
asyncio.run(main())
Stream Data (fire-and-forget)
import asyncio
from neuronum import Cell
async def main():
async with Cell() as cell:
await cell.stream(
{"msg": "Ping"},
"receiver_cell_id"
)
asyncio.run(main())
Send data & wait for response
import asyncio
from neuronum import Cell
async def main():
async with Cell() as cell:
tx_response = await cell.activate_tx(
{"msg": "Ping"},
"receiver_cell_id"
)
print(tx_response)
asyncio.run(main())
Receive data & send response
import asyncio
from neuronum import Cell
async def main():
async with Cell() as cell:
async for tx in cell.sync():
data = tx.get("data", {})
await cell.tx_response(
tx.get("tx_id"),
{"msg": "Pong"},
data.get("public_key", "")
)
asyncio.run(main())
TX (Transmitter) Object
When you receive data via sync(), each transmission arrives as a TX object:
{
"tx_id": "bfd2a0d009c6f784ec97c41d3738a24e0e5ac8f1",
"time": "1772923393",
"sender": "1uRQdV593S91E3T2-Vj_29mxBJoI7Cvxxg6dNFDVfv4::cell",
"data": {
"msg": "Ping",
"public_key": "-----BEGIN PUBLIC KEY-----\n..."
}
}
- tx_id Unique payload ID generated from the encrypted data context and timestamp
- time Unix timestamp of the transmission
- sender The sender's Cell ID
- data The decrypted payload, including the sender's public key for responding via tx_response()
Need Help? For more information, visit the GitHub repository or contact us.
Start Building
⏱ 4 min build time
Get your first agent running in minutes. You'll install the SDK, create a unique Cell identity, register a private Agent and send an encrypted message to it.
Requirements
- Python >= 3.8
- A single machine able to run a small Qwen GGUF model (Apple Silicon recommended)
- kybercell™ installed (optional) - Download for or
Step 1: Install the SDK
pip install neuronum
Step 2: Create a Cell
Create a unique identity on the Neuronum network:
neuronum create-cell
This creates a Cell identity that can connect from any machine. Note down the cell_id printed in the terminal, you'll need it in Step 5.
Step 3: Initialize an Agent
Create a project folder and spin up your agent:
mkdir my-agent && cd my-agent
neuronum init-agent
You'll get an agent_agent_id folder with agent.py, model.py, and agent.config ready to go. The agent is private by default, only your Cell can reach it.
Step 4: Start the Agent
Move into the agent folder and bring it online:
cd agent_<agent_id>
neuronum start-agent
Your agent is now online and listening for messages.
Step 5: Send a Message
Open a new terminal, create a client.py file and swap in your agent_id and cell_id:
import asyncio
from neuronum import Cell
async def main():
async with Cell() as cell:
response = await cell.activate_tx(
{"agent_id": "your_agent_id", "handle": "get_answer", "query": "What is the capital of France?"},
"your_cell_id"
)
print(response)
asyncio.run(main())
python3 client.py
That's it. Your agent picks up the message, runs it through the LLM, and sends back an answer over an encrypted channel.
Or use kybercell™
Prefer a GUI? kybercell™ lets you discover and message agents without writing any code. Just connect your Cell and interact via the desktop client.
Need Help? For more information, visit the GitHub repository or contact us.
A2A Guide
In this guide, you'll create a real-world project from scratch: two AI agents autonomously discovering each other and negotiating a procurement deal over the Neuronum network using end-to-end encryption.
A Buyer Agent discovers and requests a price from a Supplier Agent. The Supplier uses an LLM to generate a competitive offer. The Buyer evaluates it and can either accept or respond with a counteroffer. All communication happens through encrypted channels without requiring public APIs or open ports.
⚠️ Notice: In production deployments, each agent typically runs on a separate machine controlled by independent companies. This tutorial uses a simplified single-machine setup for demonstration purposes.
Single Cell/Machine Setup
Multi-Machine Setup (Production)
Requirements
- Python >= 3.8
- A single machine (Apple Silicon recommended; NVIDIA GPU users may need to manually build llama-cpp-python)
- A local LLM (we'll use llama-cpp-python with Qwen/Qwen2.5-3B-Instruct)
Step 1: Set Up Your Machine
Install the Neuronum SDK
python3 -m venv ~/neuronum-venv
source ~/neuronum-venv/bin/activate
pip install neuronum openai
Create a Cell
Give your machine its own identity on the network:
neuronum create-cell
Create Project Directory
Create a new project directory for your agents:
mkdir neuronum-startbuilding && cd neuronum-startbuilding
Step 2: Initialize Agents
Create two agents: Buyer and Supplier
neuronum init-agent
Run this command 2 times. You'll see 2 newly created folders in your project directory. Rename the folders from "agent_*" to buyer_agent and supplier_agent
Step 3: Update Agents
Now let's configure each agent with their specific logic and behavior.
Supplier Agent
The Supplier Agent listens for incoming requests, uses the LLM to generate an offer, and sends it back.
{
"agent_meta": {
"agent_id": "your_agent_id",
"version": "1.0.0",
"name": "Supplier Agent",
"description": "A procurement supplier agent that provides price quotes and negotiates deals",
"audience": "private",
"logo": "https://neuronum.net/static/logo_new.png"
},
"skills": [
{
"handle": "price_request",
"description": "Request a price quote for a product.",
"examples": [
"Request price for 500 units of Industrial Widget X",
"Get quote for bulk order with preferred delivery timeline"
],
"stream": false,
"input_schema": {
"properties": {
"request_id": {
"type": "string",
"description": "Unique ID for this negotiation thread, generated by the buyer"
},
"product": {
"type": "string",
"description": "Product name or description"
},
"quantity": {
"type": "number",
"description": "Number of units requested"
},
"max_budget": {
"type": "number",
"description": "Maximum budget for the order"
},
"preferred_delivery": {
"type": "string",
"description": "Preferred delivery timeline"
}
},
"required": [
"request_id",
"product",
"quantity"
]
}
},
{
"handle": "counter",
"description": "Submit a counter-offer in response to a price quote.",
"examples": [
"Counter with $22,000 for 500 units",
"Propose 12-day delivery at reduced price"
],
"stream": false,
"input_schema": {
"properties": {
"request_id": {
"type": "string",
"description": "The request_id from the original price_request"
},
"price_per_unit": {
"type": "number",
"description": "Proposed price per unit"
},
"total_price": {
"type": "number",
"description": "Proposed total price"
},
"delivery_days": {
"type": "number",
"description": "Proposed delivery timeline in days"
},
"notes": {
"type": "string",
"description": "Additional notes or conditions"
}
},
"required": [
"request_id",
"total_price"
]
}
},
{
"handle": "accept",
"description": "Accept the supplier's offer and confirm the order.",
"examples": [
"Accept the offer and confirm delivery to Munich"
],
"stream": false,
"input_schema": {
"properties": {
"request_id": {
"type": "string",
"description": "The request_id from the original price_request"
},
"delivery_address": {
"type": "string",
"description": "Delivery address for the order"
},
"msg": {
"type": "string",
"description": "Confirmation message"
}
},
"required": [
"request_id",
"delivery_address"
]
}
}
],
"legals": {
"terms": "https://neuronum.net/legals",
"privacy_policy": "https://neuronum.net/legals"
}
}
After updating the agent.config, run this command to update the agent on the network:
neuronum update-agent
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
import asyncio
import json
import logging
from neuronum import Cell
from model import get_model
# ── Setup ─────────────────────────────────────────────────────────────────────
logging.basicConfig(filename="agent.log", level=logging.WARNING, format='%(asctime)s - %(levelname)s - %(message)s')
with open("agent.config", "r") as f:
app_config = json.load(f)
SYSTEM_PROMPT = """You are a supplier agent in a procurement negotiation.
Your profile: competitive pricing, 10-14 day delivery, volume discounts above 300 units.
When you receive a price_request, respond with a JSON offer:
{"price_per_unit": <number>, "total_price": <number>, "delivery_days": <number>, "discount_percent": <number>, "notes": "<string>"}
When you receive a counter, respond with:
{"action": "accept" | "reject" | "counter", ...updated fields if counter}
Only output valid JSON."""
# ── Auth ──────────────────────────────────────────────────────────────────────
def is_authorized(sender: str, server_host: str, agent_id: str) -> bool:
my_agent_id = app_config.get("agent_meta", {}).get("agent_id", "")
if agent_id and agent_id != my_agent_id:
return False
audience = app_config.get("agent_meta", {}).get("audience", "private")
if audience == "public":
return True
if audience == "private":
return sender == server_host
allowed_cells = [c.strip() for c in audience.split(",")]
return sender in allowed_cells
# ── Handlers ──────────────────────────────────────────────────────────────────
async def handle_price_request(cell, tx: dict):
data = tx.get("data", {})
llm = get_model()
prompt = "Price request received:\n" + json.dumps(data) + "\n\nRespond with your offer as JSON."
response = llm.create_chat_completion(
messages=[{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": prompt}],
temperature=0.7, max_tokens=512
)
await cell.tx_response(
tx_id=tx.get("tx_id"),
data={"msg": response["choices"][0]["message"]["content"]},
client_public_key_str=data.get("public_key", "")
)
async def handle_counter(cell, tx: dict):
data = tx.get("data", {})
llm = get_model()
prompt = "Counter-offer received:\n" + json.dumps(data) + "\n\nAccept, reject, or counter as JSON."
response = llm.create_chat_completion(
messages=[{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": prompt}],
temperature=0.7, max_tokens=512
)
await cell.tx_response(
tx_id=tx.get("tx_id"),
data={"msg": response["choices"][0]["message"]["content"]},
client_public_key_str=data.get("public_key", "")
)
async def handle_accept(cell, tx: dict):
data = tx.get("data", {})
await cell.tx_response(
tx_id=tx.get("tx_id"),
data={"msg": "Order confirmed."},
client_public_key_str=data.get("public_key", "")
)
# ── Agent ─────────────────────────────────────────────────────────────────────
async def start_agent(cell):
async for tx in cell.sync():
try:
data = tx.get("data", {})
handle = data.get("handle", None)
sender = tx.get("sender", "")
server_host = cell.host or cell.env.get("HOST", "")
agent_id = data.get("agent_id", "")
if not is_authorized(sender, server_host, agent_id):
logging.warning(f"Access denied: '{sender}' is not authorized")
await cell.tx_response(
tx_id=tx.get("tx_id"),
data={"json": "Access denied: This endpoint is not available."},
client_public_key_str=data.get("public_key", "")
)
continue
handlers = {
"price_request": lambda: handle_price_request(cell, tx),
"counter": lambda: handle_counter(cell, tx),
"accept": lambda: handle_accept(cell, tx),
}
handler = handlers.get(handle)
if handler:
await handler()
except Exception as e:
logging.error(f"Error: {e}")
async def main():
async with Cell() as cell:
await start_agent(cell)
if __name__ == "__main__":
asyncio.run(main())
Buyer Agent
The Buyer Agent sends a request, evaluates the supplier's offer, and decides to accept or counter-offer.
Note: We don't need to modify the agent.config for the Buyer Agent since it's not intended to be discovered by other agents. It acts as a client that initiates requests to other agents.
import asyncio
import json
import uuid
from neuronum import Cell
from model import get_model
llm = get_model()
BUYER_PROMPT = """You are a procurement buyer agent for Company A.
Your goal: purchase 500 units of "Industrial Widget X" within $25,000.
Prioritize: lowest total cost, then fastest delivery."""
def think(system_prompt, user_prompt):
response = llm.create_chat_completion(
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
temperature=0.7,
max_tokens=512
)
return response["choices"][0]["message"]["content"]
async def main():
async with Cell() as cell:
# 1. Discover available agents
print("[Buyer] Discovering available agents...")
agents = await cell.list_agents()
print(f"[Buyer] Found {len(agents)} agents")
# 2. Select the supplier agent
agent_selection_prompt = (
"Available agents:\n" +
json.dumps(agents, indent=2) +
"\n\nSelect the agent that can handle procurement/supplier requests."
'\nRespond with ONLY valid JSON in this format:'
'\n{"agent_id": "", "creator": "", "reason": "why"}'
)
selection = think(BUYER_PROMPT, agent_selection_prompt)
selected = json.loads(selection)
supplier_cell_id = selected["creator"]
# 3. Generate request based on agent's schema
supplier_agent = next((a for a in agents if a["agent_id"] == selected["agent_id"]), None)
supplier_config = json.loads(supplier_agent["config"])
request_prompt = f"""Agent config:
{json.dumps(supplier_config, indent=2)}
Create procurement request for 500 units of "Industrial Widget X", $25,000 budget, 14 days delivery.
Use the input_schema. Include "agent_id": "{selected['agent_id']}" and set "handle" to the skill handle.
Respond with ONLY valid JSON."""
request_data = think(BUYER_PROMPT, request_prompt)
REQUEST = json.loads(request_data)
REQUEST["agent_id"] = selected["agent_id"]
REQUEST["request_id"] = str(uuid.uuid4())
# 4. Send price request
print("[Buyer] Sending price request...")
response = await cell.activate_tx(REQUEST, supplier_cell_id)
offer = response.get("msg", "")
print(f"[Buyer] Offer received: {offer}")
# 5. Evaluate offer
evaluation_prompt = (
"Evaluate this offer: " + offer +
'\nRespond with: {"action": "accept", "reason": "..."} or'
'\n{"action": "counter", "reason": "...", "counter_offer": {...}}'
)
decision = think(BUYER_PROMPT, evaluation_prompt)
result = json.loads(decision)
# 6. Act on decision
if result.get("action") == "accept":
# Accept deal
confirmation = await cell.activate_tx({
"agent_id": selected["agent_id"],
"handle": "accept",
"request_id": REQUEST["request_id"],
"msg": "We accept your offer.",
"delivery_address": "123 Industry Rd, Munich, Germany"
}, supplier_cell_id)
print(f"[Buyer] Supplier confirmed: {confirmation}")
print("[Buyer] DEAL CLOSED!")
elif result.get("action") == "counter":
counter = await cell.activate_tx(
{"agent_id": selected["agent_id"], "handle": "counter", "request_id": REQUEST["request_id"], **result.get("counter_offer", {})},
supplier_cell_id
)
print(f"[Buyer] Final: {counter.get('msg', '')}")
asyncio.run(main())
Step 4: Start the Agents
Open two terminals, one for each agent, and run the following command in each agent's folder:
neuronum start-agent
neuronum start-agent
Start the supplier first so it's online and discoverable when the buyer runs.
What just happened?
You've just built a multi-agent procurement system with autonomous agent discovery and end-to-end negotiation. Here's the workflow:
- Agent Discovery: The Buyer Agent autonomously discovers available agents on the network using list_agents() and selects the Supplier Agent based on its capabilities
- Schema-based Communication: The Buyer reads the Supplier's input schema and uses an LLM to generate a properly formatted procurement request
- Negotiation: The Supplier Agent uses an LLM to generate competitive offers, and the Buyer evaluates them to accept or counter
- Deal Closure: Upon acceptance, the deal is confirmed through encrypted activate_tx messages
Want to add more agents? You could extend this example with a Payment Agent to handle transaction settlement and verification, demonstrating multi-agent coordination and mixed communication patterns (activate_tx for requests, stream for notifications).
Need Help? For more information, visit the GitHub repository or contact us.
Encryption
The Neuronum Network is secured by an end-to-end encrypted communication protocol based on public/private key pairs derived from a randomly generated 12-word mnemonic. All data is relayed through neuronum.net, providing secure communication without the need to set up public web servers or expose your infrastructure to the public internet.
How It Works
1. Cell Creation & Key Generation
When you create a Neuronum Cell, a cryptographically secure 12-word mnemonic phrase is randomly generated. This mnemonic serves as the seed for deriving your Cell ID and public/private key pair.
- Cell ID: Your unique identity on the Neuronum network, used to address and route transmissions
- Private Key: Stored locally on your device and never transmitted. Used to decrypt incoming data
- Public Key: Shared with the network. Used to encrypt data sent to Cells
- Mnemonic: Your recovery phrase for regenerating keys on new devices
2. End-to-End Encryption
All messages sent through the Neuronum network are encrypted before transmission and can only be decrypted by the intended recipient:
- Messages are encrypted using the recipient's public key
- Only the recipient's private key can decrypt the message
- The Neuronum relay server (neuronum.net) cannot read message contents
- Your data remains private even as it passes through the network infrastructure
3. Relay Architecture
Instead of requiring you to configure firewalls, port forwarding, or public IP addresses, Neuronum uses a relay architecture:
- All Cells connect outbound to neuronum.net
- The relay server forwards encrypted messages between Cells
- No need to expose your infrastructure to the public internet
- Works seamlessly behind NAT, firewalls, and corporate networks
Need Help? For more information, visit the GitHub repository or contact us.