DEV Community

Mavos.by.Kyklos
Mavos.by.Kyklos

Posted on • Edited on

Build a Multi-Agent AI App That Shares Context Without Tool Calls (Python Tutorial)

In this tutorial, I'll show you how to build a multi-agent Python app where agents share live context without making tool calls to each other — using SignalMesh as an ambient context layer.

By the end you'll have:

  • A running SignalMesh instance (local or HF Space)
  • A 3-agent pipeline where agents broadcast and receive context
  • A cost comparison showing what you saved

Live demo: https://kyklos.io | GitHub: https://github.com/Ig0tU/SignalMesh


Prerequisites

  • Python 3.10+
  • Basic familiarity with AI agents (LangChain, CrewAI, or raw Python)
  • ~15 minutes

Step 1: Get SignalMesh Running

Option A — Use the public HF Space (no install):

HF_SPACE = "https://acecalisto3-signalmesh.hf.space"
# All endpoints available at this URL, no auth required
Enter fullscreen mode Exit fullscreen mode

Option B — Self-host with Docker:

git clone https://github.com/Ig0tU/SignalMesh
cd SignalMesh
docker build -t signalmesh .
docker run -p 7860:7860 signalmesh
# Now available at http://localhost:7860
Enter fullscreen mode Exit fullscreen mode

Option C — Import directly:

from signalmesh import signal_registry
# Runs in-process, no network overhead
Enter fullscreen mode Exit fullscreen mode

Step 2: Build a 3-Agent Pipeline

We'll build: ResearcherAnalystWriter, sharing context through the mesh.

The Researcher — broadcasts findings

import json
from signalmesh import signal_registry

def researcher_agent(topic: str) -> dict:
    # Imagine this calls a real API or search tool
    findings = {
        "topic": topic,
        "key_stats": ["99.97% cost reduction", "1.69µs latency"],
        "sources": ["benchmark_suite", "production_data"],
        "sentiment": "positive",
    }

    # Broadcast to the mesh — every agent can now read this
    signal_registry.broadcast(
        name=f"research_{topic.replace(' ', '_')}",
        source_type="context",
        data=findings,
    )

    print(f"Researcher: broadcast findings for '{topic}'")
    return findings
Enter fullscreen mode Exit fullscreen mode

The Analyst — tunes in, adds analysis

def analyst_agent(topic: str) -> dict:
    # Read researcher findings from mesh — no tool call, no API hit
    research = signal_registry.tune_in([topic, "research"])

    if not research:
        return {"error": "No research found in mesh"}

    raw = research[0]["content"]

    # Add analysis layer
    analysis = {
        "summary": f"Based on {len(raw['sources'])} sources",
        "confidence": "high" if raw["sentiment"] == "positive" else "medium",
        "recommendation": "proceed",
        "key_stats": raw["key_stats"],
    }

    # Broadcast analysis back to mesh for the writer
    signal_registry.broadcast("analysis_output", "context", analysis)

    print(f"Analyst: read research, broadcast analysis")
    return analysis
Enter fullscreen mode Exit fullscreen mode

The Writer — tunes in to both

def writer_agent() -> str:
    # Read both research AND analysis from mesh
    research = signal_registry.tune_in(["research"])
    analysis = signal_registry.tune_in(["analysis_output", "recommendation"])

    # Build the article from ambient context — no tool calls
    article = f"""
# {research[0]['content']['topic'].title()}

**Key finding:** {analysis[0]['content']['summary']}
**Confidence:** {analysis[0]['content']['confidence']}

Stats: {', '.join(research[0]['content']['key_stats'])}

Recommendation: {analysis[0]['content']['recommendation'].upper()}
    """.strip()

    print("Writer: built article from mesh context")
    return article
Enter fullscreen mode Exit fullscreen mode

Run the pipeline

def run_pipeline(topic: str):
    print(f"\n=== Running pipeline for: {topic} ===\n")

    researcher_agent(topic)
    analyst_agent(topic)
    article = writer_agent()

    print(f"\n=== Final Article ===\n{article}")

run_pipeline("AI agent cost optimization")
Enter fullscreen mode Exit fullscreen mode

Zero tool calls between agents. Zero redundant fetches. Each agent reads from what the previous one already put in the mesh.


Step 3: Verify with the Live API

Check what's in the mesh after your pipeline runs:

curl https://acecalisto3-signalmesh.hf.space/ui/frequencies
# Shows all active frequencies + signal counts

curl -X POST https://acecalisto3-signalmesh.hf.space/api/tune_in \
  -H "Content-Type: application/json" \
  -d '{"keywords":["research","analysis"]}'
# Returns all matching signals
Enter fullscreen mode Exit fullscreen mode

Step 4: Measure the Cost Difference

import time

# Time a traditional tool call (simulated)
def mock_tool_call():
    time.sleep(0.0008)  # 800ms simulated network call
    return {"data": "context"}

# Time a mesh tune_in
def mesh_read():
    return signal_registry.tune_in(["research"])

# Benchmark
n = 1000
t0 = time.perf_counter()
for _ in range(n): mock_tool_call()
tool_time = (time.perf_counter() - t0) / n * 1e6

t0 = time.perf_counter()
for _ in range(n): mesh_read()
mesh_time = (time.perf_counter() - t0) / n * 1e6

print(f"Tool call: {tool_time:,.0f}µs avg")
print(f"Mesh read: {mesh_time:.2f}µs avg")
print(f"Speedup: {tool_time/mesh_time:,.0f}×")
Enter fullscreen mode Exit fullscreen mode

What You Built

A 3-agent pipeline where:

  • Context flows through the mesh, not through tool calls
  • Each agent can read any other agent's output without knowing it exists
  • Adding a 4th or 5th agent costs $0 in additional context read overhead

FAQ

Can agents write to frequencies they don't own?
Yes — any agent can broadcast to any frequency. Use naming conventions (agent_name/output) to avoid collisions.

What if I need ordered message delivery?
SignalMesh is not a message queue — use Kafka or RabbitMQ for ordering guarantees. SignalMesh is for ambient context where "latest state" is what matters.

How do I clear a frequency?
The buffer auto-manages (last 100 signals). For explicit clearing, restart the registry or add a clear_frequency() call to your pipeline teardown.


Top comments (0)