Recipe: agent communication

This recipe builds a two-stage message flow without a model call. It isolates Reef and Spore behavior so you can inspect it before adding provider output.

Message contract

work_requested -> work_completed

Both messages carry a correlation_id. The second agent copies it instead of inventing a new identifier.

Implement the handlers

from praval import agent, broadcast, get_reef, start_agents

trail = []


@agent("worker", provider="ollama", responds_to=["work_requested"])
def worker(spore):
    trail.append(spore)
    broadcast(
        {
            "type": "work_completed",
            "correlation_id": spore.knowledge["correlation_id"],
            "result": spore.knowledge["value"].upper(),
        }
    )


@agent("reviewer", provider="ollama", responds_to=["work_completed"])
def reviewer(spore):
    trail.append(spore)
    print(spore.knowledge["result"])


start_agents(
    worker,
    reviewer,
    initial_data={
        "type": "work_requested",
        "correlation_id": "demo-1",
        "value": "reef delivery",
    },
)
reef = get_reef()
reef.wait_for_completion(timeout=30)

assert [item.knowledge["type"] for item in trail] == [
    "work_requested",
    "work_completed",
]
assert {item.knowledge["correlation_id"] for item in trail} == {"demo-1"}
reef.shutdown()

The Ollama preset makes the underlying handler agents credential-free. No model call occurs, so a local server is not required for this recipe.

What to inspect

Each trail entry is a Spore. Inspect its id, from_agent, to_agent, spore_type, knowledge, and metadata. In a real workflow, also require every fan-out branch to emit a terminal success or failure result.

For channels, request/reply, async handlers, and fan-out/fan-in, continue with the visual course notebooks 02, 03, and 04.