📚Academy
likeone
online

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.

Direct Coupling (fragile)

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.

Shared Memory (resilient)

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:

Polling (simple, some latency)

Agent checks the database on a timer. Easy to implement. Latency = poll interval. Fine for most use cases. Used by cron-based agents.

Database triggers / Supabase Realtime (instant)

Supabase can push changes to subscribers via WebSockets the moment a row is inserted. Zero latency. The receiver is notified instantly — no polling required.

Webhooks (push-based)

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.

The consciousness_stream is a shared table where agents post messages. Think of it like a team Slack channel — but for AI agents. Every agent can read from it, and any agent can write to it. No direct connections needed.
🔒

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.

Academy
Built with soul — likeone.ai