Backbone for distributed AI systems.
Actor runtime. Streaming-first. Zero dependencies. Built-in discovery.
Pulsing is a distributed actor runtime built in Rust, designed for Python. Connect AI agents and services across machines β no Redis, no etcd, no YAML. Just pip install pulsing.
π Zero Dependencies β Pure Rust + Tokio, no NATS/etcd/Redis
β‘ Streaming-first β Native support for streaming responses, built for LLM token generation
π Built-in Discovery β SWIM/Gossip protocol for automatic cluster management
π Same API Everywhere β Same await actor.method() for local and remote Actors
pip install pulsingimport asyncio
import pulsing as pul
from pulsing.agent import runtime
@pul.remote
class Greeter:
def __init__(self, display_name: str):
self.display_name = display_name
def greet(self, message: str) -> str:
return f"[{self.display_name}] Received: {message}"
async def chat_with(self, peer_name: str, message: str) -> str:
# Use Greeter.resolve() to get a typed proxy
peer = await Greeter.resolve(peer_name)
return await peer.greet(f"From {self.display_name}: {message}")
async def main():
async with runtime():
# Create two agents
alice = await Greeter.spawn(display_name="Alice", name="alice")
bob = await Greeter.spawn(display_name="Bob", name="bob")
# Agent communication
reply = await alice.chat_with("bob", "Hello!")
print(reply) # [Bob] Received: From Alice: Hello!
asyncio.run(main())That's it! @pul.remote turns a regular class into a distributed Actor, and Greeter.resolve() enables agents to discover and communicate with each other.
| Scenario | Example | Description |
|---|---|---|
| Quick start | examples/quickstart/ |
Get started in 10 lines |
| Multi-Agent collaboration | examples/agent/pulsing/ |
AI debate, brainstorming, role-playing |
| Distributed LLM inference | pulsing actor router/vllm |
GPU cluster inference service |
| Integrate AutoGen | examples/agent/autogen/ |
One line to go distributed |
| Integrate LangGraph | examples/agent/langgraph/ |
Execute graphs across nodes |
| Agent workspace CLI | pulsing agent init |
Pulsing Agent β multi-agent in your repo |
| Agent tools & environment | examples/python/forge_minimal.py |
Pulsing Forge β sandboxed shell, files, plan |
A general-purpose tool and environment runtime for AI agents β run shell commands, edit files, and manage plans inside a configurable sandbox. Embed in any agent framework, or deploy isolated workers via Pulsing Actors.
from pulsing.forge import ForgeEnvironment
env = ForgeEnvironment(cwd=".")
env.runtime().call_tool("shell_command", {"cmd": "pytest -q", "workdir": "."})Docs: Forge chapter Β· Package README: python/pulsing/forge/README.md
Workspace-scoped multi-agent SDK + CLI β init a .pulsing/ workspace, wake agents on the cluster, and collaborate with Forge tools.
pip install pulsing[agent]
pulsing agent init
pulsing agent wake --agents guide
pulsing agent say guide "run pytest"Docs: workspace demo Β· SDK: from pulsing.agent import Agent, spawn_agent
Multiple AI Agents working in parallel and communicating:
from pulsing.agent import agent, runtime, llm
@agent(role="Researcher", goal="Deep analysis")
class Researcher:
async def analyze(self, topic: str) -> str:
client = await llm()
return await client.ainvoke(f"Analyze: {topic}")
@agent(role="Reviewer", goal="Evaluate proposals")
class Reviewer:
async def review(self, proposal: str) -> str:
client = await llm()
return await client.ainvoke(f"Review: {proposal}")
async with runtime():
researcher = await Researcher.spawn(name="researcher")
reviewer = await Reviewer.spawn(name="reviewer")
# Parallel work and collaboration
analysis = await researcher.analyze("AI trends")
feedback = await reviewer.review(analysis)# Run MBTI personality discussion example
python examples/agent/pulsing/mbti_discussion.py --mock --group-size 6
# Run parallel idea generation example
python examples/agent/pulsing/parallel_ideas_async.py --mock --n-ideas 5Develop locally, scale seamlessly to clusters:
# Standalone mode (development)
async with runtime():
agent = await MyAgent.spawn(name="agent")
# Distributed mode (production) β just add address
async with runtime(addr="0.0.0.0:8001"):
agent = await MyAgent.spawn(name="agent")
# Other nodes auto-discover
async with runtime(addr="0.0.0.0:8002", seeds=["node1:8001"]):
agent = await resolve("agent") # Cross-node transparent callOut-of-the-box GPU cluster inference:
# Start Router (OpenAI-compatible API)
pulsing actor pulsing.serving.Router --addr 0.0.0.0:8000 --http_port 8080 --model_name my-llm
# Start vLLM Worker (can have multiple)
pulsing actor pulsing.serving.VllmWorker --model Qwen/Qwen2.5-0.5B --addr 0.0.0.0:8002 --seeds 127.0.0.1:8000
# Test
curl http://localhost:8080/v1/chat/completions \
-d '{"model": "my-llm", "messages": [{"role": "user", "content": "Hello"}]}'Have existing AutoGen/LangGraph code? One-line migration:
# AutoGen: Replace runtime
from pulsing.autogen import PulsingRuntime
runtime = PulsingRuntime(addr="0.0.0.0:8000")
# LangGraph: Wrap the graph
from pulsing.langgraph import with_pulsing
distributed_app = with_pulsing(app, seeds=["gpu-server:8001"])TensorMessage carries opaque metadata and ordered, contiguous CPU buffers
without putting tensor payloads in the normal pickle envelope. The caller (for
example PulsingQueue) moves CUDA tensors to CPU, makes them contiguous, and
encodes dtype, shape, byte order, and TensorDict structure in metadata. Pulsing
only transports those bytes and buffers.
from array import array
import pulsing as pul
cpu_buffer = array("f", range(6))
message = pul.TensorMessage(
metadata=b"...", # dtype, shape, byte order, and TensorDict structure
buffers=[memoryview(cpu_buffer).cast("B")],
version=1,
)Clear-text remote connections use a pooled raw TCP path by default. It sends
the header, metadata, and original buffers with vectored I/O and reads every
payload directly into its final receive allocation. TLS and
PULSING_TENSOR_TRANSPORT=http2 use the packed HTTP/2 compatibility path.
See the complete transport design and the runnable TCP example.
examples/
βββ quickstart/ # β 5-minute quickstart
β βββ hello_agent.py # First Agent
βββ agent/
β βββ pulsing/ # ββ Multi-Agent apps
β β βββ mbti_discussion.py # MBTI personality discussion
β β βββ parallel_ideas_async.py # Parallel idea generation
β βββ autogen/ # AutoGen integration
β βββ langgraph/ # LangGraph integration
βββ python/ # ββ Basic examples
β βββ ping_pong.py # Actor basics
β βββ cluster.py # Cluster communication
β βββ tensor_message_fast_path.py # Tensor transport
β βββ ...
βββ rust/ # Rust examples
- Zero external dependencies: Pure Rust + Tokio, no NATS/etcd/Redis needed
- Gossip protocol: Built-in SWIM protocol for node discovery and failure detection
- Location transparency: Same API for local and remote Actors
- Streaming messages: Native support for streaming requests/responses (LLM-ready)
- Type safety: Rust Behavior API provides compile-time message type checking
Pulsing/
βββ crates/ # Rust core
β βββ pulsing-actor/ # Actor System
β βββ pulsing-py/ # Python bindings
βββ python/pulsing/ # Python package
β βββ actor/ # Actor API
β βββ agent/ # Agent toolkit
β βββ autogen/ # AutoGen integration
β βββ langgraph/ # LangGraph integration
βββ examples/ # Example code
βββ docs/ # Documentation
- Rust β₯ 1.75
- Python β₯ 3.10
- uv (recommended package manager)
- just (task runner:
cargo install justorbrew install just)
# 1. Install Python dependencies
uv sync --extra dev
# 2. Compile Rust core and install (run again after any Rust changes)
uv run maturin developjust dev # Compile and install in development mode
just test # Run all tests (Rust + Python)
just test-python # Python tests only
just fmt # Format code (Rust + Python)
just lint # Lint check
just check # Full pre-commit check (format + lint + test)
just cov # Generate coverage reportSee CONTRIBUTING.md for a detailed guide on the development workflow.
Apache-2.0