Two Neurons. Two Axons.
One Synapse. One Cortex.
A Cortex (orchestrator Dendrite) load-balances prompts across two workers in a simple rotation. Each ask(prompt) dispatches the TASK to the next worker, then resolves a future when the matching AGENT_OUTPUT returns. The same code runs over five language × transport stacks — pick one below.
The same topology, expressed across language and transport. Slide between tabs — the routing logic never changes; only the imports, the synapse you connect to, and how you launch it do.
The dev devsynapse ships in the CLI — no external broker to install.
# SDK + CLI + httpx (used by the HuggingFace Neuron wrapper) pip install -e cosmonapse-core/packages/python-sdk pip install -e cosmonapse-core/packages/cli pip install httpx # Hugging Face token — read scope is enough $ export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx
One bus, one namespace. The CLI also streams every Signal that crosses it to stdout, so the synapse terminal doubles as a Doppler.
$ cosmo synapse start memory --namespace=quickstart URL: cosmo://127.0.0.1:7070 Namespace: quickstart Transport: TCP + NDJSON (single-host dev only) ────────────────────────────────────────────────
A Worker Dendrite that hosts a single Axon wrapping a HuggingFace Neuron. Copy it to worker_b.py and change only MY_ID to "worker-b" (and the model, if you like).
import asyncio import os from cosmonapse import Axon, Neuron, Dendrite, connect_synapse SYNAPSE_URL = "cosmo://127.0.0.1:7070" # ← the only line that changes per transport NAMESPACE = "quickstart" MY_ID = "worker-a" # worker-b is identical but for this line async def main(): axon = Axon( neuron_id=MY_ID, neuron_fn=Neuron( source="huggingface", endpoint="https://router.huggingface.co", model="meta-llama/Llama-3.1-8B-Instruct", api_key=os.environ["HF_TOKEN"], use_chat_api=True, max_new_tokens=128, ), capabilities=["text-generation", "chat"], ) synapse = await connect_synapse(SYNAPSE_URL) dendrite = Dendrite(synapse=synapse, namespace=NAMESPACE, dendrite_id=MY_ID) dendrite.attach_axon(axon) try: async with dendrite: print(f"{MY_ID} ready") await asyncio.Event().wait() finally: await synapse.close() asyncio.run(main())
A Cortex is just a Dendrite that dispatches tasks and collects results. itertools.cycle(WORKERS) picks the next target; a trace_id → Future map resolves the caller when the matching AGENT_OUTPUT returns.
import asyncio, itertools from cosmonapse import Dendrite, connect_synapse, new_trace_id SYNAPSE_URL = "cosmo://127.0.0.1:7070" WORKERS = ("worker-a", "worker-b") class RoundRobinCortex: """A Dendrite that round-robins prompts across a worker pool.""" def __init__(self, dendrite, workers): self._dendrite = dendrite self._cycle = itertools.cycle(workers) self._pending = {} @dendrite.on_agent_output async def _on_output(sig): fut = self._pending.pop(sig.trace_id, None) if fut and not fut.done(): fut.set_result(sig.payload.get("output", {})) async def ask(self, prompt, timeout=60.0): target = next(self._cycle) # ← round-robin pick trace_id = new_trace_id() fut = asyncio.get_running_loop().create_future() self._pending[trace_id] = fut await self._dendrite.dispatch_task( neuron=target, input={"prompt": prompt}, trace_id=trace_id, ) print(f"→ dispatched to {target}") return await asyncio.wait_for(fut, timeout=timeout) async def main(): synapse = await connect_synapse(SYNAPSE_URL) dendrite = Dendrite(synapse=synapse, namespace="quickstart", dendrite_id="cortex", heartbeat_s=0) cortex = RoundRobinCortex(dendrite, WORKERS) prompts = ["haiku: the sun", "haiku: the moon", "haiku: the sea", "haiku: the wind"] try: async with dendrite: for p in prompts: result = await cortex.ask(p) print(f" ← {result.get('response', '').strip()}") finally: await synapse.close() asyncio.run(main())
Separate terminals, one Synapse shared by all. Start the bus first, then the workers, then the driver.
# terminal 1 — the bus $ cosmo synapse start memory --namespace=quickstart # terminal 2 — first worker $ python worker_a.py # terminal 3 — second worker $ python worker_b.py # terminal 4 — the cortex $ python cortex.py
Watch the prompts alternate A, B, A, B in the cortex output:
$ python cortex.py
→ dispatched to worker-a
← Golden disc ascends — silence breaks into light.
→ dispatched to worker-b
← Pale lantern in the dark — tides remember her face.
→ dispatched to worker-a
← Salt sighs against stone, an old song the wind forgot.
→ dispatched to worker-b
← Invisible river — it bends the wheat into prayer.More workers. Add worker-c and extend the WORKERS list — the cycle handles any length.
Weighted round-robin. Replace the cycle with a custom sequence — e.g. ["a", "a", "b"] to send 2-of-3 to worker A.
Dynamic membership. Pass a registry_store to the Cortex and call find_neurons(capability="chat") instead of a static list — workers can join and leave at runtime.
Change transport. Every other tab is the same topology — only the install, the synapse you connect to, and the launch commands change. The routing logic is byte-for-byte identical.