PROTOCOL 07 / MULTI-AGENT

Communication Between Agents

1–2 sessions Foundation Eden research series

Learning outcomes

  • Keep agents independent
  • Define typed messages
  • Use inboxes and a message bus
  • Deliver asynchronously
  • Deduplicate messages
  • Turn messages into sourced memories

1. Communication is not shared consciousness

Eve enters the garden with her own state, goals, tools and memory. Adam may tell her that he found water at (7, 2), but he cannot write directly into her memory or force her next action. Eve may trust the report, question it, verify it or ignore it.

Communication transports information; it does not guarantee cooperation or truth.

2. Agent, bus and inbox

  • Agent: produces and consumes messages while preserving its own decision process.
  • talk tool: requests a message send through the usual validation pipeline.
  • MessagingService: validates sender, recipient, length, limits and format.
  • Inbox: stores pending messages for one recipient.
  • MemoryService: decides whether a received message becomes a retained episode.
ADAMtalk()Request to communicate
BUSValidate + queueID and delivery tick
EVEReceiveInbox item
EVEInterpretBelieve, doubt or ignore

3. Define AgentMessage

from typing import Literal from pydantic import BaseModel, ConfigDict, Field class AgentMessage(BaseModel): """A typed, traceable message between garden agents.""" model_config = ConfigDict(extra="forbid") message_id: str = Field(min_length=1) sender_id: str = Field(min_length=1) recipient_id: str = Field(min_length=1) kind: Literal["inform", "question", "reply"] content: str = Field(min_length=1, max_length=300) sent_tick: int = Field(ge=0) deliver_tick: int = Field(ge=0) reply_to: str | None = None

Pydantic checks the structure. The application must still verify the authenticated sender, a known recipient, a unique message ID and a valid delivery policy. System-generated IDs and ticks must not be trusted to model output.

4. Treat successful sending precisely

A successful talk() result means “accepted and queued”. It does not mean delivered, read, remembered, believed or acted upon. Represent those transitions as separate events.

AGENT BOUNDARY

Adam can share the content “Water at (7, 2)”. Eve alone decides whether to store a memory with source agent_message.

5. Use queues for independent timing

Adam may be waiting for Ollama while Eve acts. A queue allows a sender and recipient to operate at different times. For a learning prototype, asyncio.Queue can model delivery. Durable delivery later requires persistence because an in-memory queue disappears when the program stops.

import asyncio class Inbox: """Async inbox owned by one agent.""" def __init__(self) -> None: self._messages: asyncio.Queue[AgentMessage] = asyncio.Queue() async def deliver(self, message: AgentMessage) -> None: await self._messages.put(message) async def receive(self) -> AgentMessage: return await self._messages.get()

6. Failure, duplicates and traceability

Reject unknown recipients and oversized content without producing partial side effects. Keep processed message IDs so a retry cannot create the same memory twice. Record queued, delivered, consumed and rejected events, each correlated with the originating tool request.

EXPERIMENT A

Manual delivery

Send one typed message without any LLM.

EXPERIMENT B

Permissions

Prevent Adam from sending under Eve's identity.

EXPERIMENT C

Tick delay

Queue at tick 12 and deliver at tick 13.

EXPERIMENT D

Interpretation

Store a message-derived memory with explicit provenance.

Protocol completion

Eve receives Adam's report without any code mutating her private memory directly. Duplicate delivery is harmless, every transition is traceable and the message can remain untrusted until verified.