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 pulsingimportasyncioimportpulsingaspulfrompulsing.agentimportruntime@pul.remoteclassGreeter:
def__init__(self, display_name: str):
self.display_name=display_namedefgreet(self, message: str) ->str:
returnf"[{self.display_name}] Received: {message}"asyncdefchat_with(self, peer_name: str, message: str) ->str:
# Use Greeter.resolve() to get a typed proxypeer=awaitGreeter.resolve(peer_name)
returnawaitpeer.greet(f"From {self.display_name}: {message}")
asyncdefmain():
asyncwithruntime():
# Create two agentsalice=awaitGreeter.spawn(display_name="Alice", name="alice")
bob=awaitGreeter.spawn(display_name="Bob", name="bob")
# Agent communicationreply=awaitalice.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.
frompulsing.forgeimportForgeEnvironmentenv=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:
frompulsing.agentimportagent, runtime, llm@agent(role="Researcher", goal="Deep analysis")classResearcher:
asyncdefanalyze(self, topic: str) ->str:
client=awaitllm()
returnawaitclient.ainvoke(f"Analyze: {topic}")
@agent(role="Reviewer", goal="Evaluate proposals")classReviewer:
asyncdefreview(self, proposal: str) ->str:
client=awaitllm()
returnawaitclient.ainvoke(f"Review: {proposal}")
asyncwithruntime():
researcher=awaitResearcher.spawn(name="researcher")
reviewer=awaitReviewer.spawn(name="reviewer")
# Parallel work and collaborationanalysis=awaitresearcher.analyze("AI trends")
feedback=awaitreviewer.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)asyncwithruntime():
agent=awaitMyAgent.spawn(name="agent")
# Distributed mode (production) — just add addressasyncwithruntime(addr="0.0.0.0:8001"):
agent=awaitMyAgent.spawn(name="agent")
# Other nodes auto-discoverasyncwithruntime(addr="0.0.0.0:8002", seeds=["node1:8001"]):
agent=awaitresolve("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 runtimefrompulsing.autogenimportPulsingRuntimeruntime=PulsingRuntime(addr="0.0.0.0:8000")
# LangGraph: Wrap the graphfrompulsing.langgraphimportwith_pulsingdistributed_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.
fromarrayimportarrayimportpulsingaspulcpu_buffer=array("f", range(6))
message=pul.TensorMessage(
metadata=b"...", # dtype, shape, byte order, and TensorDict structurebuffers=[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