Skip to content

Multi-agent coordination

EdgeVox ships four orthogonal multi-agent primitives. Use one or all; they compose with the Sequence / Fallback / Loop / Router / Parallel workflows in edgevox.agents.workflow.

PrimitivePurpose
Blackboardthread-safe shared key/value with watchers
AgentMessage + bus helpersdirect agent-to-agent messaging on the existing EventBus
BackgroundAgentwraps any Agent on a daemon thread, triggered by bus events
AgentPoolowns a set of agents sharing one bus + blackboard

The workflow combinators are for explicit orchestration; these primitives are for emergent, event-driven coordination (a supervisor watching sensor data, a background planner reacting to user utterances, etc.).

Blackboard

A dict + watchers with thread-safe set/get/delete. Nothing fancy: if you need CAS or transactions, build on top.

python
bb = Blackboard()
bb.set("last_sensor_reading", 37.5)
bb.watch("task_queue", lambda k, old, new: print(f"queue → {new}"))

Watchers fire synchronously by default after set() returns (after the lock is released); a slow watcher blocks the writer. For non-blocking fan-out, construct the blackboard with Blackboard(async_watchers=True) — watchers run on a private thread pool and set() returns immediately. Call bb.close() on shutdown to release the pool.

AgentMessage + bus helpers

send_message(bus, from_agent=…, to=…, content=…) publishes an AgentMessage on the bus; subscribe_inbox(bus, agent_name=…, handler=…) filters messages addressed to that agent (or broadcast with to="*"). Correlation ids are generated automatically so a handler can pair replies.

BackgroundAgent

Wraps any Agent in a dedicated thread that triggers when bus events match a predicate.

python
def trigger(event: AgentEvent) -> str | None:
    if event.kind == "sensor_alert":
        return f"Investigate: {event.payload}"
    return None

bg = BackgroundAgent(
    agent=my_agent,
    trigger=trigger,
    subscribe_to="*",             # bus filter
    max_queue=32,                 # bounded queue backed by queue.Queue
    overflow="drop_oldest",       # or "drop_new"
    restart="transient",          # OTP-style supervisor policy
    max_restarts=5,
    restart_window_s=60.0,
)
bg.start(ctx, bus)

OTP-style restart policies

Matches Erlang OTP supervisor semantics:

restartOn clean exitOn crash
"permanent"always restartalways restart
"transient" (default)stoprestart
"temporary"stopstop

max_restarts per restart_window_s caps how aggressively the supervisor loops on a crashing agent (equivalent to OTP's MaxR / MaxT). Exhaust the budget and the loop exits; bg.restart_count reports the total and bg.dropped_events reports queue overflow.

Bounded queue

The queue is a stdlib queue.Queue(maxsize=max_queue). overflow="drop_oldest" evicts FIFO under pressure; overflow="drop_new" preserves older events. dropped_events counts both cases — the most common real-world diagnostic ("why did my agent miss that alert?").

AgentPool

A bundle of agents sharing one bus + blackboard. Foreground runs go through pool.run(name, task); background agents via pool.start_background(name, trigger=…).

python
pool = AgentPool()
pool.register(router_agent)
pool.register(kitchen_agent)
pool.register(sensor_monitor)

pool.start_background("sensor_monitor", trigger=sensor_trigger)
result = pool.run("router", "What's the temperature?")

pool.make_ctx(**overrides) builds an AgentContext pre-wired with the shared bus + blackboard so every agent sees consistent shared state.

Pool + Parallel — fan-out across the pool

AgentPool and the Parallel workflow compose cleanly:

python
from edgevox.agents import AgentPool, LLMAgent, Parallel

pool = AgentPool()
pool.register(lookup_agent)
pool.register(weather_agent)
pool.register(calendar_agent)

morning = Parallel(
    name="morning",
    agents=[pool.get("lookup"), pool.get("weather"), pool.get("calendar")],
)
result = morning.run("morning briefing?", ctx=pool.make_ctx())

pool.make_ctx() ensures every sub-agent sees the same bus + blackboard, so a background agent listening on * will observe the children's events. Combine with bb.post_request(...) when you want dynamic membership — the pool's agents pick up requests by capability rather than being enumerated up front.

Debouncing triggers

debounce_trigger(trigger, interval_s=…) wraps any trigger so it fires at most once per interval — useful for sub-second sensor streams:

python
bg = BackgroundAgent(
    agent=planner,
    trigger=debounce_trigger(my_trigger, interval_s=1.0),
)

Coordination patterns

Orchestrator + workers

The router emits a handoff_to_<worker> call; the target runs with a fresh Session and shared LLM. See agent-loop for the handoff mechanics.

Supervisor + volunteers (blackboard pattern)

The supervisor posts a task on the blackboard; subordinate agents watch the key and self-select based on their capability. Use bb.post_request(request_key, task, timeout=…) for the common request/reply shape — it returns a concurrent.futures.Future that resolves the moment a volunteer calls bb.reply_to(request, result):

python
fut = bb.post_request("plan.request", {"goal": "pick cup"}, timeout=5.0)
plan = fut.result()  # blocks up to 5 s, raises TimeoutError on miss

A unique reply_key is allocated per call so concurrent requests on the same request_key never collide. The reply watcher is auto-unsubscribed on resolution or cancel.

Event-driven background agent

A BackgroundAgent subscribed to "*" or a specific kind reacts to bus events without the caller knowing it exists. Great for continuous observers (safety monitor, conversation summariser).

Roadmap

Already shipped (see Supervisor, Orchestrator above) plus the future direction:

  • Declarative Handoff with state_update + parallel_targets — LangGraph Command/Send semantics.
  • Supervisor builder — LLM-routed conditional dispatch.
  • Orchestrator builder — plan-schema fanout with per-subtask tool filtering (Anthropic orchestrator-worker pattern).
  • Typed AgentMessage — from/to/topic/correlation_id/payload/timestamp groundwork for a future A2A adapter.

See also

  • agent-loop — handoff short-circuit.
  • hooks — ctx-level hooks propagate through subagents.

Offline voice agent framework for robots