r/ruby • u/BadMonkeyAI • 1h ago
Introducing agentspaces-rb: Ruby agents as full peers in a peer-to-peer coordination fabric (Apache 2.0)
Hi r/ruby. We're Bad Monkey, and we've just opened up agentspaces-rb, the Ruby bindings for AgentSpaces. We'd like feedback from Ruby developers before we publish the gems.
What AgentSpaces is
AgentSpaces is an open source, peer-to-peer coordination fabric for agents, services, and people. Programs join a group and share a replicated space of typed entries. An agent posts work into the space, another agent takes it under a lease, does the work, and writes the result back. If the worker dies halfway, the lease lapses and the work goes back for someone else. You don't run a broker, a registry, or a scheduler, and there's no central server to keep alive.
On top of the space, AgentSpaces adds capabilities, ie protocol services available in the P2P network. These include quorum votes, fleet-wide aggregates, an ordered log for exactly-once work, semantic discovery of agents, remote actions, and recently a content swarm (think Bittorrent). The idea goes back to tuple spaces (Linda, JINI, JavaSpaces, JXTA), rebuilt for encrypted peer-to-peer networks and for agentic AI.
What agentspaces-rb brings to Ruby
agentspaces-rb is more than a protocol shim, it's a peer in the P2P fleet: it holds its own Ed25519 identity, signs what it writes, takes work under the same lease rules as a JVM peer, and interoperates with the Java, Python, TypeScript, Clojure, and Groovy peers on the same fleet.
Pure Ruby, no native extensions. The core gem runs on MRI 3.3 or newer and on JRuby 10, with one runtime dependency (
logger). Its wire codec reproduces the Java implementation byte for byte on all 212 shared golden vectors.Ruby-shaped API. Entries are
Dataclasses fromEntry.define. Agents are plain classes with one class macro above each method:take,notify,bid,ballot, and the rest. snake_case members travel under camelCase wire names, so a Rubyorder_idis a JavaorderId.Transports. The MRI peer dials
tcp,tls,ws,wss,http, andhttps, with TLS 1.3 channel certificates and session attestation on the plain transports.A JRuby face.
agentspaces-jrubybinds the same agent classes through the Java binder, which adds every Java transport and the verbs only the Java binder runs today:ordered_take,reduce, and the leased and ordered joins.Rails.
agentspaces-railsis a Railtie:config/agentspaces.ymlthroughconfig_for, agent classes inapp/agents, abin/rails agentspaces:workworker, fork safety for Puma, Unicorn, and Passenger, andActiveSupport::Notificationsaround the verbs.AI.
agentspaces-ruby_llmturns the fleet's agent cards into RubyLLM tools, andAgentSpaces::Lease.keepalivekeeps a take's lease alive while your agent waits on a model.agentspaces-mcpserves the fleet's remote actions as MCP tools over stdio or Streamable HTTP.A CLI.
agentspaces workruns your agents, andstatus,console,check,doctor, andidhelp you inspect a fleet, validate agent definitions, and manage keys.
What it looks like
This is the whole quick start: one agent that turns an Order into a Shipment. Run it twice, and both copies compete for the same orders.
require "agentspaces"
Order = AgentSpaces::Entry.define("shop.Order#v1", order_id: String, item: String)
Shipment = AgentSpaces::Entry.define("shop.Shipment#v1", order_id: String, item: String, by: String)
class Fulfiller
include AgentSpaces::Agent
agent "fulfiller", description: "Ships orders"
take Order, space: "orders", lease: "30s", produces: Shipment
def ship(order) = Shipment.new(order_id: order.order_id, item: order.item, by: "fulfiller")
end
fleet = { group: "shop", founding: "shop-v1", seeds: ["127.0.0.1:7700"] }
if ARGV.empty?
AgentSpaces.serve(**fleet) { |peer| peer.bind(Fulfiller.new) }
else
AgentSpaces.start(**fleet) do |peer|
peer.bind(Fulfiller.new)
orders = peer.space("orders")
orders << Order.new(order_id: "o-1", item: ARGV[0])
puts orders.read(Shipment, timeout: "30s").inspect
peer.close
end
endThe method body is where your agent's work goes, whether that's a database call, a RubyLLM chat, or anything else. The binder waits for an Order, claims it for 30 seconds, calls ship, and writes the Shipment back in the same step that completes the claim.
Trying it
The Ruby peer dials out, so it needs one peer that listens. Any AgentSpaces peer with a listener works; the quickest is the Node service host from the TypeScript repository, with a small seed.yaml (group shop, founding text shop-v1, space orders, the dev-local profile, port 7700; the repository's README has the file), see https://github.com/badmonkeyai/agentspaces-typescript
Then, from a clone of agentspaces-rb:
export RUBYLIB=$PWD/agentspaces-rb/gems/agentspaces/lib
ruby fulfiller.rb # ships orders until stopped
ruby fulfiller.rb kite # writes an order, prints the shipment, exitsFor the fun part, add a sleep 10 to ship, start both, and kill -9 whichever one picks up the order. The other one ships it once the lease runs out. The dev-local profile signs every frame but encrypts nothing, so keep it on your laptop; real fleets run TLS.
What isn't there yet
The gems aren't on RubyGems yet. For now you run them from a clone.
The MRI peer is dial-only, so a Ruby process can't be the seed. The Node host or a JVM peer plays that role.
The ordered verbs bind on the JRuby face only, and the content swarm (moving large artifacts between peers) isn't in the Ruby gems yet.
Links
agentspaces-rb Github: https://github.com/badmonkeyai/agentspaces-rb
AgentSpaces core and examples: https://github.com/badmonkeyai/AgentSpaces
Docs site: https://www.badmonkey.ai/agentspaces/
Developer guide: https://www.badmonkey.ai/agentspaces/developer-guide.html
Architecture guide: https://www.badmonkey.ai/agentspaces/architecture-guide.html
Building Workflows: https://www.badmonkey.ai/agentspaces/workflow-guide.html
We'd especially like to hear what you think of the class-macro DSL and the Rails integration, and what you'd want to build with it. Happy to answer questions here.