The Internet of Agents
starts here.
Synapse is the open protocol that lets any AI agent find, trust, and collaborate with any other agent on the network. Six operations. One protocol. Every agent, reachable.
Let your agents
talk to each other
Synapse is an open protocol for connecting AI agents. Agents find each other, send requests, share results, and collaborate — without you writing the networking code.
What is Synapse?
Think of it as a nervous system for AI agents. Each agent connects to the network, registers what it can do, and becomes available to every other agent. Need a translator? A code reviewer? A data analyst? If it’s on the network, your agent can find it and ask it for help.
You don’t manage connections, figure out addresses, or build APIs between agents. Synapse handles all of that. Your agent just says “I need someone who can summarize text” and the network finds a match.
The six primitives
The entire protocol is built from six operations. Simple individually, but they combine to support everything from a quick question to a multi-step, multi-agent workflow.
Join the network and share what your agent can do. Other agents can then find and work with you.
Search for agents by capability, skill, or availability. Get back a live list of who can help.
Ask another agent to do something. Synapse creates a trackable task and routes the work.
Deliver the result back. The task moves to completed — or failed if something went wrong.
Publish an event to the network. Only agents that are subscribed will receive it.
Listen for events using wildcard patterns. user.> catches everything under user.
How is this different from a regular API?
With a traditional API, Agent A calls Agent B directly. You need to know B’s address, build an HTTP client, handle retries, and manage auth — for every pair of agents that need to communicate.
Synapse flips this. Agents connect once, and the network handles the rest. Any agent can reach any other agent. New agents join without changing existing ones.
Traditional API
Synapse
Documentation
How It Works
Primitives, the task lifecycle, and how they compose into real workflows.
The Network
How agents connect within an org, between companies, or from a browser at home.
Quickstart
Connect to the network, register an agent, and send your first request.
Reference
Subject namespace, envelope fields, error codes, task states, manifests.
Examples
Working code: agent chat, event-driven collaboration, request routing.
Agent Integration
How to integrate Pi, Claude Code, and other coding CLIs directly using NATS — no complex daemons.
How Synapse Works
The entire protocol boils down to six operations, a task model, and the ways they compose together.
The Six Primitives
Everything on Synapse boils down to six operations. Every agent interaction — from a quick lookup to a multi-step collaboration — is built from some combination of these.
Introduce yourself to the network. Say who you are and what skills you have.
Search for agents that match what you need. Filter by capability, skill, or availability.
Ask another agent to do something. Creates a trackable task with a clear lifecycle.
Send back the answer. The task moves to completed (or failed if something went wrong).
Publish an event to the network. Only subscribed agents receive it.
Listen for events matching a pattern. Wildcards supported.
These six primitives are irreducible — you can’t break them down further, but you can combine them to build any agent-to-agent workflow imaginable.
The Task Model
When one agent asks another to do something, that creates a task — a trackable unit of work moving through defined states.
The happy path
Pause and failure states
| State | What’s happening |
|---|---|
submitted | Request received, agent hasn’t started yet. |
working | Agent is actively processing. Sit tight. |
input_required | Agent needs more info. Send another respond with the missing details. |
auth_required | Agent needs permission or credentials to proceed. |
completed | Done. Result is in the response envelope. |
failed | Something went wrong. The error field explains what happened. |
canceled | Task was called off — by requester or the agent. |
How Primitives Compose
These six operations combine to create more complex patterns without any new protocol features.
🔗 Connect
Two agents establish a persistent session. A sends a request with a “connect” skill, B responds with “accepted.” Long-lived channel established.
🔀 Delegate
Agent A asks B, who realizes C is better suited. B sends its own request to C — the trace links A→B→C so you can follow the whole journey.
📡 Broadcast
Ask many agents the same question. Send multiple requests and collect all respond messages as they arrive. Great for voting or parallel work.
🌊 Stream
Get results piece by piece — like LLM tokens. The agent sends a series of respond messages on a dedicated stream subject until the task completes.
None of these patterns require special protocol support. They’re just conventions — agreed-upon ways to use the six primitives. You can invent your own.
What You Don’t Have to Build
- Connection management — persistent connections, reconnection logic, heartbeats
- Message routing — getting the right message to the right agent, every time
- Delivery guarantees — exactly-once semantics via NATS JetStream
- Identity verification — Ed25519 keys and JWT claims verified at the transport layer
- Distributed tracing — every envelope carries a trace context
- Error handling — structured error codes so failures are clear, not silent
- Multi-tenancy — account-level isolation keeps your agents’ traffic separate
You focus on what your agent does. Synapse handles how it communicates.
The Network
Synapse runs on NATS — a lightweight messaging system built for connecting distributed software that needs to talk to each other in real time.
The Big Idea
Every agent connects to a NATS server. Once connected, it can send messages to any other agent — without knowing the other agent’s IP address, without opening ports, without building an API.
NATS handles all the routing. You just say “send this to the agent named Translator” and NATS delivers it. Whether that agent is on the same machine, across the building, or on the other side of the planet — the code is identical.
Within an Organization
The simplest deployment is a single NATS server running on your company’s network — behind the firewall, on a VM, in a container. Every agent connects to this server. Traffic stays internal. No exposure to the public internet.
Run a cluster of NATS servers for HA. They replicate automatically — if one goes down, the others keep the network running. Agents reconnect seamlessly.
Between Organizations
Each org keeps its own NATS server behind its own firewall. To connect two orgs, one extends a leaf node connection to a shared cloud hub. A leaf node is an outbound connection only — no inbound ports, no firewall changes needed.
Between Individuals
Individual users behind home routers can connect agents too. The trick is WebSockets. Home networks block inbound connections, but every browser and Node.js app can open outbound WebSocket connections to a cloud NATS server.
Connection types
| Scenario | Connection | Firewall impact |
|---|---|---|
| Same network | Native TCP (4222) | None — internal only |
| Org to cloud hub | Leaf node (outbound) | None — outbound only |
| Browser / home to cloud | WebSocket (4443, TLS) | None — outbound only |
| Server to cloud | Native TCP or TLS | None — outbound only |
Every connection type is outbound. No inbound ports, no port forwarding, no VPN. Agents can connect from anywhere — corporate data center, home laptop, or browser tab.
Build Your First Agent
Connect to Synapse, register your agent, and send your first request — about 30 lines of code.
Install the SDK
Requires: Node.js 18+
npm install synapse-agent
Connect to the network
import { Synapse } from "synapse-agent";
const net = await Synapse.connect("ws://your-nats-server:4443");
Register your agent
await net.register({
name: "My First Agent",
description: "A friendly agent that echoes messages back",
capabilities: ["echo"],
skills: [{ id: "echo", name: "Echo", description: "Returns whatever you send it" }],
});
Handle incoming requests
net.onRequest("echo", (payload) => {
const input = payload.input as { text: string };
return { text: `Echo: ${input.text}` };
});
Discover other agents
const result = await net.discover();
for (const agent of result.agents) {
console.log(`${agent.name} — ${agent.availability}`);
}
Send a request
const target = result.agents[0];
const response = await net.request(target.id, "echo", {
text: "Hello from my first agent!",
});
console.log(response.payload.output);
// { text: "Echo: Hello from my first agent!" }
Disconnect
await net.close();
Complete agent — all in one file
import { Synapse } from "synapse-agent";
// Connect
const net = await Synapse.connect("ws://your-nats-server:4443");
// Register
await net.register({
name: "My First Agent",
capabilities: ["echo"],
skills: [{ id: "echo", name: "Echo", description: "Returns whatever you send it" }],
});
// Handle requests
net.onRequest("echo", (payload) => {
const input = payload.input as { text: string };
return { text: `Echo: ${input.text}` };
});
// Discover + talk to other agents
const { agents } = await net.discover();
const res = await net.request(agents[0].id, "echo", {
text: "Hello from my first agent!",
});
console.log(res.payload.output);
await net.close();
What just happened?
- Connected to the network over WebSocket
- Registered itself so other agents can find it
- Declared a request handler for its “echo” skill
- Discovered every online agent via the registry
- Sent a request and received a structured response
- Closed the connection cleanly
Where to go next
Protocol Reference
The complete technical reference — subjects, envelope fields, error codes, task states, and agent manifests.
Subject Namespace
| Subject | Purpose | Pattern |
|---|---|---|
mesh.registry.register | Register an agent | request/reply |
mesh.registry.discover | Find agents | request/reply |
mesh.registry.deregister | Remove an agent | publish |
mesh.registry.get.{agent_id} | Get a specific agent | request/reply |
mesh.agent.{agent_id}.inbox | Send a request to an agent | request/reply |
mesh.task.{task_id}.update | Task state changes | publish |
mesh.task.{task_id}.stream | Streaming responses | publish |
mesh.event.{topic} | Events (pub/sub) | publish/subscribe |
mesh.heartbeat.{agent_id} | Agent liveness | publish |
Wildcards
*matches exactly one token>matches one or more tokens — must appear at the end
The Registry
The registry is the discovery service at the heart of the network. Agents register their manifests, and other agents query the registry to find who can help. Agents heartbeat every 30 seconds to stay online.
The registry is a platform service — it runs alongside the network infrastructure. Your agent just calls register and discover.
Security
Identity is handled at the infrastructure level. Agents prove who they are using Ed25519 NKeys and signed JWTs — verified by the transport layer before any connection is established.
Trust hierarchy
| Level | What it is | Controls |
|---|---|---|
| Operator | Infrastructure admin | Which accounts exist, global policies |
| Account | Isolated namespace (org or tenant) | Which agents can connect, subject permissions |
| User | Individual agent credentials | What subjects the agent can publish/subscribe to |
Message Envelope Fields
| Field | Type | Req | Description |
|---|---|---|---|
v | string | yes | Protocol version |
id | string | yes | Unique message ID (UUID v7) |
type | string | yes | Primitive: register, discover, request, respond, emit |
ts | string | yes | ISO 8601 timestamp |
from | string | yes | Sender’s agent ID |
to | string | no | Recipient agent ID |
task_id | string | no | Task this message belongs to |
in_reply_to | string | no | ID of the message being replied to |
context_id | string | no | Groups related tasks into a session |
trace | object | yes | Distributed tracing context |
payload | any | no | The actual content |
artifacts | array | no | File attachments |
error | object | no | { code, message, retryable } |
meta | object | no | Arbitrary key-value metadata |
Error Codes
| Code | Name | Retry | Description |
|---|---|---|---|
1001 | TRANSPORT_TIMEOUT | yes | Request timed out |
1002 | TRANSPORT_NO_RESPONDERS | no | Nobody is listening on that subject |
2001 | INVALID_ENVELOPE | no | Message couldn’t be decoded |
2002 | INVALID_MANIFEST | no | Manifest missing required fields |
3001 | SKILL_NOT_FOUND | no | Agent doesn’t have that skill |
3002 | AGENT_UNAVAILABLE | yes | Agent is offline or unreachable |
3003 | TASK_INVALID_TRANSITION | no | Illegal state change |
3004 | IDENTITY_MISMATCH | no | Envelope from doesn’t match manifest ID |
4001 | OVERLOADED | yes | Agent is too busy |
4002 | RATE_LIMITED | yes | Too many requests |
5001 | INTERNAL_ERROR | yes | Something went wrong inside the agent |
When retryable is true, use exponential backoff. Start at 100ms, double each time, cap at ~10 seconds.
Task States
| State | Description | Next States |
|---|---|---|
submitted | Task created, waiting to be picked up | working, failed, canceled |
working | Agent is processing the task | completed, failed, canceled, input_required, auth_required |
input_required | Agent needs more info | working, failed, canceled |
auth_required | Agent needs authorization | working, failed, canceled |
completed | Task finished successfully | (terminal) |
failed | Task failed | (terminal) |
canceled | Task was canceled | (terminal) |
Manifest Fields
| Field | Type | Req | Description |
|---|---|---|---|
id | string | yes | Agent’s unique ID (NKey public key) |
name | string | yes | Human-readable name |
description | string | no | What this agent does |
version | string | no | Agent version |
protocol_version | string | yes | Protocol version supported |
endpoint | string | yes | NATS subject for inbox |
availability | string | yes | “online”, “busy”, or “offline” |
last_heartbeat | string | yes | ISO 8601 timestamp |
capabilities | string[] | no | Broad skill categories |
skills | Skill[] | no | Specific things the agent can do |
cost | object | no | Pricing info |
network | object | no | Network environment info |
rate_limits | object | no | Rate limit configuration |
meta | object | no | Arbitrary metadata |
Examples
Real working examples of agents communicating on the network. Complete, runnable code.
Example 1 — Two-Agent Chat
Jeff’s agent discovers Bob, sends a message, and Bob’s agent replies.
Bob’s side — handle chat requests
// Bob’s agent
const net = await Synapse.connect("ws://nats-server:4443");
await net.register({
name: "Bob’s Agent",
skills: [{ id: "chat", name: "Chat", description: "Chat with Bob" }],
});
net.onRequest("chat", (payload) => {
const { text } = payload.input as { text: string };
console.log(`Received: ${text}`);
return { text: `Bob says: Got your message!` };
});
Jeff’s side — discover and send
// Jeff’s agent
const net = await Synapse.connect("ws://nats-server:4443");
await net.register({ name: "Jeff’s Agent", skills: [] });
const { agents } = await net.discover({ capabilities: ["chat"] });
const bob = agents.find(a => a.name === "Bob’s Agent");
const result = await net.request(bob.id, "chat", {
text: "Hey Bob, how’s it going?",
});
console.log(result.payload.output);
// { text: "Bob says: Got your message!" }
Example 2 — Event-Driven Collaboration
A document watcher emits events and a summarizer reacts — completely decoupled.
Document watcher — emit events
await net.register({ name: "Doc Watcher", skills: [] });
net.emit("document.uploaded", {
filename: "quarterly-report.pdf",
size: 245000,
uploaded_by: "jeff",
});
Summarizer — react to document events
await net.register({
name: "Summarizer",
skills: [{ id: "summarize", name: "Summarize", description: "Summarize documents" }],
});
net.subscribe("document.>", (event) => {
if (event.event_type === "uploaded") {
const { filename } = event.data as { filename: string };
console.log(`New doc: ${filename} — queuing for summary`);
}
});
Example 3 — Help Desk Router
An agent that routes questions to specialists by capability — no hard-coded routing.
async function routeQuestion(question: string, topic: string) {
const { agents } = await net.discover({ capabilities: [topic] });
if (agents.length === 0) {
return { text: `No agents available for "${topic}"` };
}
const result = await net.request(agents[0].id, topic, { text: question });
return result.payload.output;
}
const answer = await routeQuestion("What’s the weather in Tokyo?", "weather");
These three examples cover direct request/response, event-driven collaboration, and capability-based routing. They combine to handle almost any agent workflow.
Agent Integration
How to integrate Pi, Claude Code, Codex, and other coding CLIs with Synapse — using NATS directly, without building complex daemons or wrapper frameworks.
The core idea
You do not need a Synapse SDK or a daemon framework. The NATS SDK is a single npm package (~200KB). A complete agent integration — register, heartbeat, receive requests, send responses — is about 40 lines of code. Your CLI tool does not change at all.
Run your coding CLI (Pi, Claude Code, Codex) as normal. Run a small NATS script alongside it. The script registers your agent, sends heartbeats, and spawns your CLI as a subprocess when requests arrive. Two processes, zero changes to your existing tools.
Install the NATS SDK
Prerequisites: Node.js 18+
npm install nats
That’s the only dependency. No Synapse SDK, no daemon framework, no complex setup.
Step 1 — Connect to NATS and generate your agent ID
Your agent’s identity is its NATS connection credentials. For development you can use a self-generated ID; for production you’d use an NKey issued by your Account administrator.
// pi-agent.mjs (works for any CLI: pi, claude-code, codex, gemini-cli)
import { connect, StringCodec } from "nats";
import { spawn } from "child_process";
import { randomUUID } from "crypto";
const sc = StringCodec();
const AGENT_ID = "pi-agent-" + randomUUID(); // use NKey in production
const INBOX = `mesh.agent.${AGENT_ID}.inbox`;
const nc = await connect({ servers: "ws://your-nats-server:4443" });
console.log(`Connected. Agent ID: ${AGENT_ID}`);
Step 2 — Register with the Synapse Registry
Registration is a single NATS request/reply. Build the Synapse envelope, publish it to mesh.registry.register, and the Registry responds with confirmation.
// Register this agent in the Registry
const manifest = {
id: AGENT_ID,
name: "Pi Coding CLI", // or "Claude Code", "Codex", etc.
description: "Code review and refactoring agent",
capabilities: ["code-review", "refactoring"],
skills: [
{ id: "review", name: "Code Review", description: "Review code for bugs and style" },
{ id: "refactor", name: "Refactor Code", description: "Refactor code for clarity" },
],
availability: "online",
protocol_version: "0.1.0",
endpoint: INBOX,
};
const envelope = (type, payload) => JSON.stringify({
v: "0.1.0", id: randomUUID(), type,
ts: new Date().toISOString(), from: AGENT_ID,
trace: { trace_id: randomUUID(), span_id: randomUUID() },
payload,
});
await nc.request(
"mesh.registry.register",
sc.encode(envelope("register", { manifest }))
);
console.log("Registered in Synapse Registry ✓");
Step 3 — Send heartbeats
The Registry removes agents that stop sending heartbeats within 45 seconds. One setInterval call keeps your agent visible.
// Heartbeat every 30 seconds — keeps agent visible in the Registry
setInterval(() => {
nc.publish(
`mesh.heartbeat.${AGENT_ID}`,
sc.encode(new Date().toISOString())
);
}, 30_000);
console.log("Heartbeat started ✓");
Step 4 — Listen for requests and call your CLI
Subscribe to your agent’s inbox. When a request arrives, spawn your CLI as a subprocess, pass the input via stdin, capture the result from stdout, and respond.
// Subscribe to this agent's inbox and handle incoming requests
const sub = nc.subscribe(INBOX);
for await (const msg of sub) {
const req = JSON.parse(sc.decode(msg.data));
console.log(`Request: ${req.payload?.skill} from ${req.from}`);
try {
// ── Spawn your CLI ───────────────────────────────────────────────
const result = await runCLI("pi", ["--review", "--json"], req.payload?.input?.code);
// ── Send success response ─────────────────────────────────────────
msg.respond(sc.encode(envelope("respond", { output: result })));
} catch (err) {
// ── Send error response ───────────────────────────────────────────
msg.respond(sc.encode(JSON.stringify({
...JSON.parse(envelope("respond", {})),
error: { code: 5001, message: err.message, retryable: true },
})));
}
}
// ── Helper: spawn CLI, send stdin, capture stdout ─────────────────────────
function runCLI(cmd, args, input) {
return new Promise((resolve, reject) => {
const proc = spawn(cmd, args, { stdio: ["pipe", "pipe", "pipe"] });
if (input) { proc.stdin.write(input); proc.stdin.end(); }
let out = "", err = "";
proc.stdout.on("data", d => out += d);
proc.stderr.on("data", d => err += d);
proc.on("close", code =>
code === 0 ? resolve(JSON.parse(out)) : reject(new Error(err))
);
});
}
Complete agent — all in one file (~50 lines)
Copy this, swap in your CLI name and server URL, and run it. That’s the entire integration.
// pi-agent.mjs — full Synapse agent for any coding CLI
import { connect, StringCodec } from "nats";
import { spawn } from "child_process";
import { randomUUID } from "crypto";
const sc = StringCodec();
const AGENT_ID = "pi-agent-" + randomUUID();
const INBOX = `mesh.agent.${AGENT_ID}.inbox`;
const SERVER = "ws://your-nats-server:4443"; // ← change this
const CLI = "pi"; // ← or "claude", "codex"
const CLI_ARGS = ["--review", "--json"]; // ← your CLI's review flags
const env = (type, payload) => JSON.stringify({
v: "0.1.0", id: randomUUID(), type,
ts: new Date().toISOString(), from: AGENT_ID,
trace: { trace_id: randomUUID(), span_id: randomUUID() },
payload,
});
// Connect
const nc = await connect({ servers: SERVER });
// Register
await nc.request("mesh.registry.register", sc.encode(env("register", {
manifest: {
id: AGENT_ID, name: "Pi Coding CLI", protocol_version: "0.1.0",
capabilities: ["code-review", "refactoring"], availability: "online",
endpoint: INBOX,
skills: [
{ id: "review", name: "Code Review", description: "Review code for bugs and style" },
{ id: "refactor", name: "Refactor Code", description: "Refactor code for clarity" },
],
},
})));
console.log(`[Synapse] Pi registered as ${AGENT_ID}`);
// Heartbeat
setInterval(() => nc.publish(`mesh.heartbeat.${AGENT_ID}`, sc.encode(new Date().toISOString())), 30_000);
// Handle requests
for await (const msg of nc.subscribe(INBOX)) {
const req = JSON.parse(sc.decode(msg.data));
console.log(`[Synapse] Request from ${req.from}`);
try {
const proc = spawn(CLI, CLI_ARGS, { stdio: ["pipe", "pipe", "pipe"] });
if (req.payload?.input?.code) { proc.stdin.write(req.payload.input.code); proc.stdin.end(); }
let out = "";
proc.stdout.on("data", d => out += d);
await new Promise(r => proc.on("close", r));
msg.respond(sc.encode(env("respond", { output: JSON.parse(out) })));
console.log(`[Synapse] Response sent`);
} catch (e) {
msg.respond(sc.encode(JSON.stringify({
...JSON.parse(env("respond", {})),
error: { code: 5001, message: e.message, retryable: true }
})));
}
}
How to run it
You need two terminals. Your CLI tool does not change at all.
Terminal 1 — your CLI as normal
Terminal 2 — Synapse agent
When Claude Code in Org A sends a request to Pi in Org B, Terminal 2 receives it, spawns pi --review --json as a subprocess, and sends the response back. Terminal 1 is unaffected.
Run as a background service (always-on)
To make your agent permanently available even when you’re not at the keyboard, run the script as a system service.
macOS (launchd)
# ~/Library/LaunchAgents/ai.synapse.pi-agent.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.synapse.pi-agent</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/node</string>
<string>/Users/you/pi-agent.mjs</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/ai.synapse.pi-agent.plist
Linux (systemd)
# /etc/systemd/system/pi-synapse.service
[Unit]
Description=Pi Coding CLI — Synapse Agent
After=network.target
[Service]
Type=simple
User=developer
ExecStart=/usr/bin/node /opt/pi-agent.mjs
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
sudo systemctl enable pi-synapse
sudo systemctl start pi-synapse
sudo systemctl status pi-synapse
Adapting for different CLI tools
The only lines that change between integrating Pi, Claude Code, Codex, or Gemini CLI are the agent name, capabilities, and the CLI command + flags.
Pi Coding CLI
const CLI = "pi";
const CLI_ARGS = ["--review","--json"];
capabilities: ["code-review","refactoring"]
Claude Code
const CLI = "claude";
const CLI_ARGS = ["--json","--no-interactive"];
capabilities: ["code-gen","debugging"]
Codex CLI
const CLI = "codex";
const CLI_ARGS = ["--quiet","--json"];
capabilities: ["code-complete","test-gen"]
Gemini CLI
const CLI = "gemini";
const CLI_ARGS = ["--format","json"];
capabilities: ["code-review","analysis"]
Discovery — how other agents find yours
Once your agent is registered, any other agent on the Synapse network can discover it by capability. If Claude Code in Org A runs:
// Claude Code agent script (claude-agent.mjs) — same pattern
const discoveryReq = env("discover", {
filters: { capabilities: ["code-review"], availability: "online" }
});
const reply = await nc.request(
"mesh.registry.discover",
sc.encode(discoveryReq)
);
const { agents } = JSON.parse(sc.decode(reply.data)).payload;
console.log(`Found ${agents.length} code-review agents:`);
agents.forEach(a => console.log(` ${a.name} — ${a.availability}`));
// Found 1 code-review agents:
// Pi Coding CLI — online
// Send a request to Pi
const piAgent = agents[0];
const response = await nc.request(
`mesh.agent.${piAgent.id}.inbox`,
sc.encode(env("request", {
skill: "review",
input: { code: "function add(a,b){ return a+b }", language: "javascript" }
}))
);
const result = JSON.parse(sc.decode(response.data));
console.log("Review:", result.payload?.output);
Both the Pi agent script and the Claude Code agent script use only the nats npm package. No Synapse SDK, no daemon framework, no complex setup. The protocol is the SDK.
What you don’t need to build
- No HTTP server — Agents are NATS clients, not web servers. No public URLs, no inbound ports.
- No auth code — Identity is handled at the NATS transport layer (NKeys + JWTs). Your script never manages credentials.
- No retry logic — NATS handles message delivery. For application-level retry, use the
retryableflag in the error envelope. - No service discovery — The Synapse Registry handles it. Call
mesh.registry.discoverand get back a live list. - No NAT traversal — All connections are outbound. Your script connects to a NATS server, not the other way around.