Round robin.
Clone the examples repo, install, and run from inside the example's folder.
$ git clone https://github.com/Cosmonapse/cosmonapse-examples $ pip install cosmonapse httpx python-dotenv # a Hugging Face token with read scope, in cosmonapse-examples/.env $ echo "HF_TOKEN=hf_xxxxxxxxxxxxxxxx" >> cosmonapse-examples/.env $ cd cosmonapse-examples/03-round-robin $ python brain.py haikus # four prompts, alternating workers $ python brain.py "haiku: the sun" # one prompt, next worker in line $ python brain.py # REPL - every line rotates
worker-a -> Morning light breaks through ... worker-b -> Silver moon ascends ... worker-a -> Waves crash on the shore ... worker-b -> Whispers through the trees ...
Exact text varies - the model is stochastic. The alternation does not.
One component per file, one Dendrite per node.
The skeleton cosmo init scaffolds. Modules under neurons/, engram/, effector/ and receptors/ declare behaviour; brain.py owns deployment - which node hosts what.
03-round-robin/
config.py WORKERS = ("worker-a", "worker-b")
neurons/pool.py make_axon(worker_id) - same Neuron, different id
receptors/terminal.py the rotation
brain.py one node per worker + terminal-nodeOne Neuron, many ids.
A factory rather than a module-level AXON: an Axon attaches to exactly one Dendrite, and the brain builds one per worker id.
from cosmonapse import Axon, Neuron from config import hf_token def make_axon(neuron_id: str) -> Axon: return Axon( neuron_id=neuron_id, neuron_fn=Neuron( source="huggingface", endpoint="https://router.huggingface.co", model="meta-llama/Llama-3.1-8B-Instruct", api_key=hf_token(), use_chat_api=True, max_new_tokens=128, ), capabilities=["text-generation", "chat"], )
Pick the next id, ask with it.
A Receptor's target is usually fixed at construction, but ask() takes neuron= or capabilities= per call. So the command picks the next worker from itertools.cycle and asks it. ask() opens the Pathway on the trace and resolves on that worker's AGENT_OUTPUT, which is everything the old hand-rolled futures did. The commands are local=True because they choose their own target instead of handing an input to the Receptor.
import itertools from cosmonapse import CliReceptor, SignalType from config import WORKERS RECEPTOR = CliReceptor( prog="round-robin", description="Cycle prompts across a pool of identical workers.", timeout_s=60.0, ) _rotation = itertools.cycle(WORKERS) @RECEPTOR.on_result def render(sig): if sig.type is SignalType.ERROR: return f"ERROR {sig.payload.get('message')}" return sig.payload["output"]["response"].strip() @RECEPTOR.command(local=True, default=True, help="send a prompt to the next worker") async def ask(prompt: str): target = next(_rotation) # round-robin pick reply = await RECEPTOR.ask({"prompt": prompt}, neuron=target) return f"{target} -> {reply}" @RECEPTOR.command("haikus", local=True, help="four prompts, alternating workers") async def haikus(): topics = ("the sun", "the moon", "the sea", "the wind") return "\n".join([await ask(f"haiku: {t}") for t in topics])
A node per worker.
Each worker gets a Dendrite with dendrite_id set to its worker id, so it is separately addressable and separately visible in Prism. Moving worker-b to another machine is running its builder there against the same SYNAPSE_URL.
import asyncio import contextlib import signal from cosmonapse import Dendrite, MemorySynapse, connect_synapse, run_brain from config import NAMESPACE, SYNAPSE_URL, WORKERS from neurons import pool from receptors import terminal def build_worker(synapse, worker_id: str) -> Dendrite: """One pool member. Same Neuron, different id.""" node = Dendrite(synapse=synapse, namespace=NAMESPACE, dendrite_id=worker_id, role="worker") node.attach_axon(pool.make_axon(worker_id)) return node def build_terminal(synapse) -> Dendrite: node = Dendrite(synapse=synapse, namespace=NAMESPACE, dendrite_id="terminal-node", heartbeat_s=0) node.attach_receptor(terminal.RECEPTOR) return node
No fixed order needed? Give the workers identical capabilities and dispatch with capabilities=["chat"]. Dendrites with the same capability profile share a queue group and the broker load-balances, as in capability routing.
Same code, different transport.
With SYNAPSE_URL unset the brain runs on an in-process bus. Point it at a synapse and nothing in the modules changes. Prism attaches to any shared synapse and animates every Signal as it crosses.
# terminal 1 - a dev synapse (TCP, single host) $ cosmo synapse start memory --namespace=quickstart # terminal 2 - the same brain, over that synapse $ SYNAPSE_URL=cosmo://127.0.0.1:7070 python brain.py haikus # terminal 3 - Prism, the live view (http://127.0.0.1:7071) $ cosmo prism --url=cosmo://127.0.0.1:7070 -n quickstart # production transports - only the URL changes $ SYNAPSE_URL=nats://127.0.0.1:4222 python brain.py haikus $ SYNAPSE_URL=kafka://127.0.0.1:9092 python brain.py haikus