Integrating an Engram.
EngramBinding and gets recall and imprint injected; the Engram lives on another node. No token needed.Clone the examples repo, install, and run from inside the example's folder.
$ git clone https://github.com/Cosmonapse/cosmonapse-examples $ pip install cosmonapse $ cd cosmonapse-examples/06-engram-integration $ python brain.py twice "what is the meaning of life?" $ python brain.py # REPL - ask the same question twice
first call computed -> Answer to 'what is the meaning of life?': 42 second call cache -> Answer to 'what is the meaning of life?': 42
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.
06-engram-integration/ engram/context.py InMemoryEngram(engram_id="ctx") neurons/researcher.py recall, compute, imprint receptors/terminal.py ask, and twice (compute, then recall) brain.py memory-node + researcher-node + terminal-node
A backend with an address.
engram_id="ctx" is the wire address. Swapping backends is swapping the constructor; nothing that uses the memory changes.
from cosmonapse import InMemoryEngram ENGRAM = InMemoryEngram(engram_id="ctx", engram_kind="context")
Imprint ops: add, append, merge, upsert, delete (merge and upsert take a merge_key). Recall modes: first, merge, all.
A pure function, plus two helpers.
Because the Axon was built with engrams=[...], the Neuron gains keyword-only recall and imprint. Each call emits RECALL or IMPRINT on the current trace and awaits the reply from whichever node attached ctx. The binding maps a local name to the wire id, so ops can repoint the memory without editing the Neuron.
from cosmonapse import Axon, EngramBinding async def researcher(input, context, *, recall, imprint): question = input["question"] key = f"q:{question}" # 1. Look in shared memory for a prior answer to this exact question. prior = await recall("ctx", query={"merge_key": key}) if prior.hits: cached = prior.hits[0].entry["content"]["answer"] return {"answer": cached, "source": "cache"} # 2. Compute a "fresh" answer (stubbed for the demo). answer = f"Answer to {question!r}: 42" # 3. Write it back so the next call hits the cache. merge_key dedupes by # question, so repeated imprints upsert a single entry. await imprint( "ctx", op="upsert", entry={"content": {"question": question, "answer": answer}, "tags": ["qa"]}, merge_key=key, await_ack=True, deadline_ms=500, ) return {"answer": answer, "source": "computed"} # The binding maps a local name ("ctx") to the wire engram_id # (directed_id), so the Neuron addresses memory by a stable local name - # ops can repoint the backend without editing Neuron code. AXON = Axon( neuron_id="researcher", neuron_fn=researcher, capabilities=["research"], engrams=[EngramBinding(name="ctx", directed_id="ctx")], )
Ask once to compute, once to recall.
The memory lives on the Engram node, not in the interface. In the REPL a repeated question comes back from the cache; twice shows the same thing in one command.
from cosmonapse import CliReceptor, SignalType RECEPTOR = CliReceptor( neuron="researcher", input_key="question", prog="researcher", description="A Neuron with shared memory over the bus.", timeout_s=5.0, ) @RECEPTOR.command(help="ask the researcher a question") def ask(question: str): return {"question": question} @RECEPTOR.command("twice", local=True, help="ask once to compute, once to recall") async def twice(question: str): first = await RECEPTOR.ask({"question": question}) second = await RECEPTOR.ask({"question": question}) return f"first call {first}\nsecond call {second}" @RECEPTOR.on_result def render(sig): if sig.type is SignalType.ERROR: return f"ERROR {sig.payload.get('message')}" out = sig.payload["output"] return f"{out['source']:>8s} -> {out['answer']}"
Memory, Neuron and interface on three nodes.
import asyncio import contextlib import signal from cosmonapse import Dendrite, MemorySynapse, connect_synapse, run_brain from config import NAMESPACE, SYNAPSE_URL from engram import context from neurons import researcher from receptors import terminal def build_memory(synapse) -> Dendrite: node = Dendrite(synapse=synapse, namespace=NAMESPACE, dendrite_id="memory-node", role="worker") node.attach_engram(context.ENGRAM) return node def build_researcher(synapse) -> Dendrite: node = Dendrite(synapse=synapse, namespace=NAMESPACE, dendrite_id="researcher-node", role="worker") node.attach_axon(researcher.AXON) 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
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=demo # terminal 2 - the same brain, over that synapse $ SYNAPSE_URL=cosmo://127.0.0.1:7070 python brain.py twice "what is the meaning of life?" # terminal 3 - Prism, the live view (http://127.0.0.1:7071) $ cosmo prism --url=cosmo://127.0.0.1:7070 -n demo # production transports - only the URL changes $ SYNAPSE_URL=nats://127.0.0.1:4222 python brain.py twice "what is the meaning of life?" $ SYNAPSE_URL=kafka://127.0.0.1:9092 python brain.py twice "what is the meaning of life?"