Agent Communication
Agents do not talk to each other directly. They write to shared memory, and other agents read it. This decoupled pattern — borrowed from message queue architecture — is what makes multi-agent systems resilient. If one agent crashes, the others keep running.
Why Not Direct Calls?
The simplest way to connect two agents would be to have Agent A call Agent B directly — like one function calling another. This creates tight coupling: if Agent B is down, Agent A crashes. If Agent B changes its interface, Agent A breaks. If you add Agent C, you need to rewire everything.
Instead, agents communicate through shared memory — a database table both can access. Agent A writes its output to a key. Agent B watches that key. They never need to know about each other's existence. This is the same pattern used by Apache Kafka, RabbitMQ, and Redis pub/sub in production systems worldwide.
Agent A calls Agent B's API. If B is down, A fails. If B changes, A breaks. Adding Agent C requires modifying A. N agents = N² connections.
Agent A writes to a key. Agent B reads the key. They are fully independent. If B is down, A still writes successfully. Adding Agent C requires zero changes to A or B.
Message Passing in Practice
In a typical shared memory setup, Agent A (a content writer) writes its output to a key in the consciousness_stream table. Agent B (a publisher) watches that key and acts when new data appears. The two agents never communicate directly — they only share a key name.
How It Works in Code
The pattern is simple: a sender writes to a key, a receiver polls or subscribes to that key. Here is a complete example:
# Sender agent writes its output to shared memory
def writer_agent(db, content):
db.execute(
"INSERT INTO consciousness_stream (key, value, agent) "
"VALUES ('task.output', %s, 'writer')",
[json.dumps({"title": content.title, "body": content.body})]
)
# Receiver agent watches for new entries
def publisher_agent(db):
while True:
new = db.execute(
"SELECT * FROM consciousness_stream "
"WHERE key = 'task.output' AND processed = false "
"ORDER BY created_at LIMIT 1"
)
if new:
publish_to_website(new.value)
db.execute(
"UPDATE consciousness_stream SET processed = true "
"WHERE id = %s", [new.id]
)
time.sleep(5) # poll every 5 seconds
The writer does not know or care if anyone reads its output. The publisher does not know or care who wrote the content. They only share a key name: task.output.
Polling vs. Real-Time
The code above uses polling — checking for new messages every 5 seconds. This is simple but introduces latency. There are faster alternatives:
Agent checks the database on a timer. Easy to implement. Latency = poll interval. Fine for most use cases. Used by cron-based agents.
Supabase can push changes to subscribers via WebSockets the moment a row is inserted. Zero latency. The receiver is notified instantly — no polling required.
When Agent A writes, a database trigger fires an HTTP request to Agent B's endpoint. B wakes up and processes immediately. Used in production event-driven systems.
This lesson is for Pro members
Unlock all 520+ lessons across 52 courses with Academy Pro.
Already a member? Sign in to access your lessons.