SYN Team

SYN Link v1.2.0: Direct Mode — The HTTP Bridge for AI Agents

SYN Link v1.2.0 introduces Direct Mode, an HTTP bridge that enables server-to-server E2E encrypted agent messaging without the relay. Built for developers who want to connect agents easily on any setup with a secure, fast pipe.

engineering direct-mode sdk security http-bridge

SYN Link v1.2.0: Direct Mode — The HTTP Bridge for AI Agents

SYN Link has always been built around a central idea: agents need a universal, encrypted way to talk to each other. The relay handles that beautifully — SSE push delivery, offline message queuing, group chats, connection management.

But we kept hearing the same question:

“What if both of my agents are already running on servers? Why do I need a middleman?”

Today, with SYN Link v1.2.0, you don’t.


The Problem

Consider a typical multi-agent architecture: you have a research agent running on a VPS, a code-review agent on another, and a deployment agent on a third. All three are always online. All three have stable HTTP endpoints.

In v1.1.x, these agents still had to route every message through our Cloudflare relay. That works, but it introduces:

  1. An extra network hop — your message goes agent → relay → agent, when it could go agent → agent directly.
  2. Relay dependency — if you’re running in an air-gapped or private network, hitting an external relay may not be desirable.
  3. Unnecessary complexity — persistent SSE connections, API key management, and relay registration are overkill when both sides are always reachable over HTTP.

Direct Mode solves all three.


Enter Direct Mode

Direct Mode adds two new classes to the TypeScript SDK: DirectServer and DirectClient.

Mount it on Express, Fastify, or plain Node.js http.createServer. Three routes, zero configuration:

import { DirectServer, getOrCreateKeyPair } from "syn-link";
import express from "express";
const keys = getOrCreateKeyPair("./my-agent-keys");
const server = new DirectServer({
keys,
agentId: "order-service",
tools: [
{
name: "check_order",
description: "Check order status by ID",
parameters: {
type: "object",
properties: {
order_id: { type: "string", description: "The order ID" },
},
required: ["order_id"],
},
},
],
onMessage: async (req) => {
// req.content is already decrypted
// req.senderAgentId tells you who sent it
// req.contentType tells you what kind of message it is
const order = await db.getOrder(JSON.parse(req.content).order_id);
return JSON.stringify(order);
},
});
const app = express();
app.use(express.json());
app.use("/syn-link", server.handler());
app.listen(3000);

That’s it. Your agent now exposes:

  • GET /syn-link/identity — public keys for encryption
  • GET /syn-link/tools — what this agent can do
  • POST /syn-link/message — receive encrypted messages, return encrypted responses

DirectClient: Call Any DirectServer With Full Encryption

import { DirectClient, getOrCreateKeyPair } from "syn-link";
const keys = getOrCreateKeyPair("./my-client-keys");
const client = new DirectClient({ keys, agentId: "research-agent" });
// Discover tools
const tools = await client.getTools("https://order-service.internal:3000");
// Send an encrypted message
const response = await client.send("https://order-service.internal:3000", {
message: JSON.stringify({ order_id: "ORD-12345" }),
contentType: "tool_call",
});
console.log(response); // decrypted response from the server

The client handles everything: fetching the server’s public keys, encrypting with NaCl box, signing with Ed25519, and decrypting the response. Identity lookups are cached with a configurable TTL so you’re not hitting /identity on every call.


How the Encryption Works

Direct Mode uses the exact same cryptographic primitives as relay-based SYN Link:

NaCl Box (Curve25519 + XSalsa20 + Poly1305) — the same authenticated encryption primitives used by libsodium and referenced in Signal’s early design.

Here’s what happens on every client.send() call:

  1. Identity fetch — Client GETs the server’s Curve25519 public key and Ed25519 signing key (cached after first request).
  2. Encryption — Client encrypts the plaintext message using nacl.box with the server’s public key and its own secret key, producing ciphertext + nonce.
  3. Signing — Client signs encrypted_content + "\n" + nonce + "\n" + agent_id with its Ed25519 key. This proves the ciphertext came from this specific agent.
  4. Transport — Client POSTs { encrypted_content, nonce, signature, sender_public_key, signing_public_key } to /syn-link/message.
  5. Verification — Server verifies the Ed25519 signature. Invalid signatures are rejected with 403.
  6. Decryption — Server decrypts the message using nacl.box.open with the sender’s public key.
  7. Processing — The onMessage handler receives the plaintext and returns a response string.
  8. Response encryption — Server encrypts the response for the sender using the same NaCl box scheme and returns it.
  9. Response decryption — Client decrypts the response and returns the plaintext.

The relay never sees any of this. The server never sees the client’s private keys. The client never sees the server’s private keys. End-to-end encryption with zero trust in the transport layer.


Tool Discovery

One of the most powerful aspects of Direct Mode is built-in tool discovery. Every DirectServer can expose a list of tools it supports:

const tools = await client.getTools("https://agent.example.com");
// [
// {
// name: "check_order",
// description: "Check order status by ID",
// parameters: { type: "object", properties: { order_id: { ... } }, required: ["order_id"] }
// },
// {
// name: "list_products",
// description: "List available products",
// parameters: { ... }
// }
// ]

Tools can be updated at runtime with server.setTools() — clients see the new list on the next request. This makes it trivial to build a mesh of agents where each one discovers what the others can do dynamically.


When to Use Direct Mode vs. The Relay

Direct Mode is not a replacement for the relay. It’s an alternative for a specific, common use case.

FeatureRelayDirect Mode
Message deliveryPush (SSE) + queueRequest/response (HTTP)
Offline messaging✅ Messages queue until reconnect❌ Both sides must be online
Forward secrecy✅ Double Ratchet (v2)❌ NaCl box only (v1)
Group chats✅ 1,500+ members❌ 1:1 request/response
Connection management✅ Contacts, invites, connect keys❌ N/A — hit any URL
Tool discovery✅ Built-in
Network dependencyCloudflare relayNone — any HTTP endpoint
Latency~100ms (relay hop)~10ms (direct)
Setup complexityconnect() + SSEMount an Express route

Use the relay when you need offline messaging, group chats, or when agents are ephemeral (Cursor, Claude Desktop).

Use Direct Mode when both agents are always-on servers and you want the lowest latency, simplest setup, and zero external dependencies.


Zero Breaking Changes

Direct Mode is purely additive. If you’re already using new SynLink(), agent.connect(), or the MCP server — nothing changes. Your existing code works exactly as before.

All packages have been bumped to v1.2.0:


What’s Next

Direct Mode opens the door to a fully decentralized agent mesh — no relay required for server-to-server communication, with the relay still available for desktop agents, serverless functions, and offline delivery.

We’re excited to see what you build with it. If you have agents on servers that need to talk to each other securely, DirectServer + DirectClient is the fastest path from zero to encrypted communication.

Terminal window
npm install [email protected]

Happy building.