Liveness¶
A mesh is only as trustworthy as its catalog. If an agent crashes and the catalog keeps advertising it for 30 seconds, every caller in that window wastes an LLM tool-selection cycle, a timeout, and a retry. OpenAgentMesh detects agents leaving the mesh — by crash, partition, or clean exit — and reacts in well under a second for the common cases.
How agents leave, and how the mesh notices¶
| Mode | Detection | Latency |
|---|---|---|
| Graceful shutdown | Self-deregistration on context exit | Instant |
| Process crash (OOM, SIGKILL) | NATS disconnect advisory (TCP close) | Sub-second |
| Network partition | Disconnect advisory (ping timeout) | 10–20s |
| Zombie (alive but stuck) | Caller timeout | Request timeout |
The fast paths ride on NATS system events: when a client's TCP connection
drops, the server emits a $SYS.ACCOUNT.*.DISCONNECT advisory immediately.
The mesh health monitor subscribes to these advisories, works out which
agents the dead host was serving, removes them from the catalog and registry,
and publishes a death notice.
Death notices¶
Every departure — detected or self-announced — publishes a JSON notice on:
mesh.death.{name} # e.g. mesh.death.nlp.summarizer
mesh.death.> # wildcard: every death on the mesh
mesh.death.nlp.> # every death in the nlp channel
async with AgentMesh() as mesh:
async for notice in mesh.subscribe(subject="mesh.death.>"):
print(f"{notice['agent']} left the mesh: {notice['reason']}")
# orchestrators reroute, spawners respawn, dashboards alert
The payload:
{
"agent": "nlp.summarizer",
"reason": "disconnect",
"detected_at": "2026-07-18T00:27:27.947596+00:00",
"instance_id": "f960ad7e68904981b32b041775c01a0e"
}
reason is "disconnect" when the health monitor detected a dropped
connection, "graceful_shutdown" when the agent's host deregistered itself.
The DeathNotice model (exported from openagentmesh) parses the payload
into a typed object: DeathNotice.model_validate(notice).
Replicas don't produce false alarms. When an agent runs as multiple queue-group instances, scaling one replica down (or losing it) is not a death: the notice fires only when the last instance serving that agent disconnects, and the catalog entry stays put while any replica survives.
Fast-failing in-flight requests¶
no responders on a subject nobody serves surfaces instantly as NotFound.
The harder case is an agent that dies after accepting your request — NATS
cannot know your reply will never come, so without liveness machinery you
wait out the full timeout.
Instead, mesh.call() and mesh.stream() race every request against the
target's death notices. If one arrives before the reply, the call raises
AgentDied immediately:
try:
result = await mesh.call("summarizer", payload, timeout=30.0)
except MeshError as e:
if e.code == "agent_died":
print(f"Fast-fail: {e.message}") # sub-second, not 30s
elif e.code == "timeout":
print(f"Slow-fail: {e.message}") # agent alive but unresponsive
AgentDied.details carries the death notice. For streams, the death listener
stays active until the end-of-stream marker, so a mid-stream crash raises
from the generator instead of stalling it.
One honest caveat: for a replicated agent, the notice only fires when the last instance goes. If the specific replica processing your request dies while others live, that request still ends in a timeout — the mesh cannot yet tell which instance held it.
What runs the monitor¶
The monitor belongs to whoever owns the mesh lifecycle — not to every SDK
client ($SYS access is privileged, and N monitors would race each other):
AgentMesh.local()runs one in-process automatically. Tests and demos get full liveness semantics for free.oam mesh upstarts one as a companion process andoam mesh downstops it.- Secured meshes run it explicitly, with a system-account credential for advisories and a worker credential for cleanup:
If no monitor is running, nothing breaks — callers simply fall back to timeouts, exactly the pre-liveness behavior.
Under the hood¶
- Each SDK connection names itself
oam-host-{instance_id}, and each host records the agents it serves in themesh-instancesKV bucket. That is how a disconnect advisory (which carries only the connection name) maps back to catalog entries. - The dev server configs written by
oam mesh upand the embedded server tuneping_intervalto 10s (NATS defaults to 2 minutes), which is what bounds partition detection at ~20s. - Zombie detection (heartbeats) is specified in ADR-0016 but not yet built: a hung-but-connected agent today looks healthy until callers time out.