https://github.com/dvf/synapse
Rapid RPC framework for building Python services fast
https://github.com/dvf/synapse
asyncio flask framework msgpack python python3
Last synced: 4 days ago
JSON representation
Rapid RPC framework for building Python services fast
- Host: GitHub
- URL: https://github.com/dvf/synapse
- Owner: dvf
- Created: 2018-08-07T12:20:33.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2019-05-25T22:40:20.000Z (almost 7 years ago)
- Last Synced: 2025-11-28T02:03:16.519Z (6 months ago)
- Topics: asyncio, flask, framework, msgpack, python, python3
- Language: Python
- Homepage:
- Size: 72.3 KB
- Stars: 14
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README

# Synapse P2P
**A network substrate for Python agents.**
Synapse P2P is a tiny async RPC, discovery, and capability-publishing layer for building agent-to-agent networks, local agent swarms, distributed tools, peer-to-peer services, and lightweight service meshes.
It is designed to be useful to both humans and LLM agents:
- Humans get a simple Python API.
- Agents get discoverable methods, published capabilities, structured request/response messages, and machine-readable metadata.
```python
@app.endpoint("sum")
async def sum_endpoint(a: int, b: int) -> int:
return a + b
```
```python
result = await Client("127.0.0.1", 9999).call("sum", 1, 2)
```
## Why Synapse?
LLM agents need a way to find each other, ask what each other can do, and delegate work over a small, predictable protocol.
Synapse provides the substrate for that:
- **RPC**: call remote Python functions over TCP.
- **Discovery**: inspect published methods and agent capabilities.
- **Agent identity**: expose role, description, and capabilities.
- **Delegation**: send tasks to another agent through `_agent.ask`.
- **Structured protocol**: MsgPack request/response envelopes with request IDs and errors.
Think of it as a minimal network layer for agentic systems — not a framework that decides how agents think, but a substrate they can use to connect.
## Features
- **Async TCP RPC** built on `asyncio`
- **MsgPack serialization** for compact binary messages
- **Length-prefixed framing** for reliable message boundaries over TCP
- **Decorator-based endpoints** with `@app.endpoint(...)`
- **Built-in async client** with request/response handling
- **Structured responses and errors** via `RPCResponse` and `RPCError`
- **Positional and keyword arguments** for remote calls
- **Request IDs** for correlation and future persistent-connection support
- **Published method discovery** via `_synapse.methods`
- **Agent metadata discovery** via `_agent.info` and `_agent.capabilities`
- **Agent task delegation** via `_agent.ask`
- **Periodic background tasks** with `@app.background(...)`
- **Serializer abstraction** for custom protocols later
- **P2P-oriented primitives** such as node identity and XOR-distance helpers
## Installation
```bash
pip install synapse-p2p
```
For development with [uv](https://docs.astral.sh/uv/):
```bash
uv sync --group dev
uv run pytest
uv run ruff check .
uv run pyrefly check
```
## Quickstart: RPC server and client
Create a server:
```python
from synapse_p2p import Server
app = Server(address="127.0.0.1", port=9999) # or Server() for defaults
@app.endpoint("sum", description="Add two numbers")
async def sum_endpoint(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
if __name__ == "__main__":
app.run()
```
Call it from a client:
```python
import asyncio
from synapse_p2p import Client
async def main() -> None:
client = Client("127.0.0.1", 9999)
result = await client.call("sum", 1, 2)
print(result)
asyncio.run(main())
```
Output:
```text
3
```
## Quickstart: agent node
An `AgentNode` is a Synapse server that publishes agent metadata and accepts delegated work.
```python
from synapse_p2p import AgentNode
agent = AgentNode(
name="Reviewer",
role="reviewer",
description="Reviews Python code and suggests tests.",
capabilities=["python", "code-review", "pytest"],
)
@agent.task_handler
async def handle_task(task: str, context: dict):
return {
"status": "done",
"result": f"Reviewed task: {task}",
"context_keys": list(context),
}
agent.run()
```
Another agent or client can inspect it:
```python
info = await client.call("_agent.info")
capabilities = await client.call("_agent.capabilities")
```
And delegate work:
```python
result = await client.call(
"_agent.ask",
"Review this pull request",
context={"files": ["server.py", "client.py"]},
)
```
## Built-in discovery endpoints
Synapse reserves `_synapse.*` for substrate-level metadata.
### `_synapse.ping`
Health check:
```python
await client.call("_synapse.ping")
# "pong"
```
### `_synapse.methods`
Returns published RPC methods:
```python
methods = await client.call("_synapse.methods")
print(methods)
```
For this endpoint:
```python
@app.endpoint("sum")
async def sum_endpoint(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
```
The result is:
```python
[
{
"name": "sum",
"publish": True,
"description": "Add two numbers.",
}
]
```
Endpoints are published by default. Add a docstring to make them more useful to humans and agents inspecting `_synapse.methods`:
```python
@app.endpoint("image.resize")
async def resize_image(...):
"""Resize an image."""
...
```
Private endpoints can opt out:
```python
@app.endpoint("admin.restart", publish=False)
async def restart():
...
```
## Built-in agent endpoints
Synapse reserves `_agent.*` for agent-level metadata and task delegation.
### `_agent.info`
Returns agent identity:
```python
{
"name": "Reviewer",
"role": "reviewer",
"description": "Reviews Python code and suggests tests.",
"capabilities": ["python", "code-review", "pytest"],
}
```
### `_agent.capabilities`
Returns machine-readable capability descriptors:
```python
[
{
"name": "code-review",
"description": "Review Python code for correctness and maintainability.",
"input_schema": {},
"output_schema": {},
}
]
```
Capabilities can be strings or explicit descriptors:
```python
from synapse_p2p import AgentCapability, AgentNode
agent = AgentNode(
name="Researcher",
role="researcher",
capabilities=[
AgentCapability(
name="web-research",
description="Find and summarize evidence from the web.",
input_schema={"query": "string"},
output_schema={"summary": "string", "sources": "array"},
)
],
)
```
### `_agent.ask`
Delegates a task to the agent's registered task handler:
```python
await client.call(
"_agent.ask",
"Find bugs in this module",
context={"code": "..."},
)
```
The task handler receives:
```python
async def handle_task(task: str, context: dict):
...
```
## Patterns for LLM agents
### 1. Discover a peer
```python
info = await client.call("_agent.info")
methods = await client.call("_synapse.methods")
capabilities = await client.call("_agent.capabilities")
```
### 2. Select a peer by capability
```python
if "code-review" in info["capabilities"]:
result = await client.call("_agent.ask", "Review this diff", context={"diff": diff})
```
### 3. Publish tools as RPC methods
```python
@app.endpoint("filesystem.search")
async def search_files(pattern: str) -> list[str]:
"""Search files by regex."""
...
```
### 4. Hide dangerous tools
```python
@app.endpoint("shell.exec", publish=False)
async def shell_exec(command: str) -> str:
...
```
Private methods are still callable if known, but they are not advertised by `_synapse.methods`.
## Server lifecycle
Use `app.run()` for simple scripts. In larger async applications or tests, use `start()` and `stop()`:
```python
server = await app.start()
try:
...
finally:
await app.stop()
```
## Background tasks
Synapse can run recurring async background jobs alongside your RPC server:
```python
from synapse_p2p import Server
app = Server()
@app.background(5)
async def heartbeat():
print("heartbeat: server is still alive")
@app.endpoint("sum")
async def sum_endpoint(a: int, b: int) -> int:
return a + b
if __name__ == "__main__":
app.run()
```
Startup output will include the task:
```text
Background Tasks:
- heartbeat (5s)
```
The task above runs roughly every five seconds. Exceptions are logged and do not stop future runs.
## Structured protocol
Synapse sends length-prefixed MsgPack messages over TCP.
A request looks like:
```python
RPCRequest(
id="request-id",
endpoint="sum",
args=[1, 2],
kwargs={},
)
```
A successful response looks like:
```python
RPCResponse(
id="request-id",
ok=True,
result=3,
)
```
An error response looks like:
```python
RPCResponse(
id="request-id",
ok=False,
error=RPCError(code="bad_request", message="Unregistered endpoint called: nope"),
)
```
## Low-level access
Most users should use `Client`, but lower-level message types are exported if you want to build custom transports or tooling:
```python
from synapse_p2p import RPCError, RPCRequest, RPCResponse, RemoteProcedureCall
from synapse_p2p.framing import read_frame, write_frame
from synapse_p2p.serializers import MessagePackRPCSerializer
```
`RemoteProcedureCall` is kept as a backwards-compatible alias for `RPCRequest`.
## Project status
Synapse is intentionally small and evolving. The current focus is a clean async RPC and agent substrate. Future work may include:
- Peer discovery and bootstrap peer exchange
- Agent-to-agent network discovery
- Long-running jobs for agent tasks
- Persistent connections and streaming events
- Routing tables / Kademlia-style node lookup
- Handshakes and node capabilities
- Authenticated or encrypted messages
- Broadcast and gossip primitives
## Keywords
Python agent substrate, agent-to-agent networking, LLM agent RPC, multi-agent systems, agent discovery, capability discovery, Python RPC, asyncio RPC, peer-to-peer Python, P2P networking, MsgPack RPC, TCP RPC, async microservices, distributed agents, distributed workers, service-to-service communication.