* Enable adaptive verify window for ngram/draft speculation The adaptive verify window was never enabled on the split-serving path: to_embedded_openai_args hardcoded adaptive_speculative_window = false. With a fixed window, an early reject never shrank the window, so a sustained reject storm kept proposing at full depth and paying the full 2-round-trip recovery cost per token. On a WAN split this measured as ~40% throughput loss with N-gram speculation ON versus OFF, despite high per-token acceptance. Enable the adaptive window whenever speculation actually proposes a window (ngram or draft mode). The existing shrink_adaptive_window logic then narrows the window toward the observed accept depth after an early reject, cutting recovery frequency. Adds a regression test asserting ngram speculation turns the adaptive window on. * Replace speculative rollback with positional MTP n-gram pipelining * Pipeline speculative verify windows across latency * Fix positional correction and adaptive pipeline depth * Continuously refill the speculative horizon * Productionize pipelined MTP n-gram speculation * Fix speculative docs and UI formatting * Remove stale speculative projections and fix CI * Handle fragmented direct-return fallback replies * Replace speculative repair with fixed-depth positional pipeline * Expose split-stage compute overlap telemetry * Lock split topology placement * Document locked split topology * Address locked topology review feedback * Fix SPEED-Bench timing JSONL output * Bound benchmark telemetry finalization * Hash SPEED-Bench request and response pairs * mesh: stop re-applying formation-time RTT gate to operational stage streams Ported from the WAN lab branch (wip/wan-direct-prediction-return,c340f741), where it was validated live on a ~26ms WAN split. open_stage_transport_stream re-applied the formation-time MAX_SPLIT_RTT_MS ceiling to every fresh operational stream, so per-request direct-return sinks were rejected under normal WAN RTT jitter while pooled forward lanes stayed healthy - surfacing as ready-handshake timeouts and 502s on an already-admitted split. Split admission still gates eligibility via gossiped, hysteresis-smoothed RTT plus re-election; operational streams now warn and proceed. * skippy: raise return-sink ready timeout 5s->20s for cold WAN bridge setup Ported from the WAN lab branch (46108cfc). Over a WAN mesh the return sink connects to a local bridge alias, but the remote ready byte only arrives after the bridge cold-establishes a fresh stage QUIC connection (~10s budget) and the remote handler dials its local server. 5s timed out during that cold setup on a healthy ~26ms split; forward lanes already use a 20s budget. Match it. * runtime: relaunch withdrawn splits when peers return instead of ending the model task Observed live on a real WAN split (Sydney M5 <-> AU 4090): one transient direct-return 502 led periodic_check to mark the remote stage unavailable; after the 75s grace the coordinator withdrew the topology. The Withdraw event returned StartupLoopControl::Break, so startup_local_model_loop tore down and the task ended permanently - while the remote worker sat healthy, logging 'standing by for stage assignment' forever. Only recovery was manually restarting both nodes with a fresh token. Make withdraw non-terminal: a new RelaunchSplit control/outcome runs the full existing teardown, then loops back to the launch phase and re-enters wait_for_split_participants, relaunching the split when an eligible peer returns. The stop channel is checked before relaunch so explicit shutdown still wins. LocalFallback (model fits locally) is unchanged. The participant-wait loop's 30s cadence and stable-participant gating act as the natural retry throttle; no extra backoff added. --------- Co-authored-by: Michael Neale <14976+michaelneale@users.noreply.github.com> Co-authored-by: Michael Neale <michael.neale@gmail.com>
8.1 KiB
Telemetry And Metrics Plugin
mesh-llm exports metrics-only OTLP/HTTP telemetry from host runtime code when
[telemetry] config enables an explicit endpoint. No collector or project-owned
destination is hard-coded.
The external metrics plugin lives at Mesh-LLM/metrics.
It advertises metrics support through the plugin API, but it does not receive
prompts, completions, logs, traces, endpoint URLs, or raw host identifiers.
Configuration
Configure an OTLP metrics endpoint:
[telemetry]
enabled = true
service_name = "mesh-llm"
endpoint = "https://otel.example.com"
headers = { "authorization" = "Bearer TOKEN" }
export_interval_secs = 15
queue_size = 2048
[telemetry.metrics]
endpoint = "https://otel.example.com/v1/metrics"
Install and enable the optional metrics plugin when you want the plugin capability advertised:
mesh-llm plugins install metrics
[[plugin]]
name = "metrics"
[plugin.startup]
connect_timeout_secs = 75
init_timeout_secs = 90
optional = true
lazy_start = true
The startup block is optional. It is useful on slow legacy machines where the plugin process may take longer than the default startup budget, or where metrics should be advertised only after the plugin is actually used.
Endpoint precedence is:
telemetry.metrics.endpointtelemetry.endpointnormalized to/v1/metricsOTEL_EXPORTER_OTLP_METRICS_ENDPOINT, only whentelemetry.enabled = trueOTEL_EXPORTER_OTLP_ENDPOINTnormalized to/v1/metrics, only whentelemetry.enabled = true
If no endpoint is configured, telemetry export stays disabled. Ambient OTel environment variables are not consumed unless telemetry is explicitly enabled in mesh-llm config.
Exported Metrics
Request and route metrics are emitted per fronting node. A collector or
dashboard can aggregate mesh_llm_requests_inflight across nodes for a
mesh-wide in-flight request view.
Counters:
mesh_llm_model_launch_totalmesh_llm_model_launch_success_totalmesh_llm_model_launch_failure_totalmesh_llm_model_unload_totalmesh_llm_model_exit_unexpected_totalmesh_llm_model_request_totalmesh_llm_route_attempt_totalmesh_llm_guardrail_decision_totalmesh_llm_guardrail_outcome_total
Gauges:
mesh_llm_loaded_modelsmesh_llm_model_loadedmesh_llm_model_context_lengthmesh_llm_requests_inflight
Histograms:
mesh_llm_model_launch_duration_msmesh_llm_model_uptime_s
Privacy Boundary
Runtime telemetry exports metrics only. The external metrics plugin advertises a capability only. Neither path exports prompts, completions, logs, traces, hostnames, mesh gossip, relay messages, raw node IDs, raw GPU stable IDs, endpoint URLs, or prompt hashes.
Guardrail telemetry follows the same boundary. It exports only bounded labels for guardrail mode, contract kind, decision, bypass reason, parser stage, and retry bucket. It does not export prompt text, completion text, schemas, tool arguments, raw tool names, reserved sentinel names, request paths, endpoints, or hostnames.
Guardrail v1 is validated emulation, not hard constrained decoding. Streaming is
pass-through, no tool execution happens inside the guardrail layer, and real
tools plus strict structured output stays unsupported in v1. See
docs/design/OPENAI_GUARDRAILS.md for the rollout contract and evidence path.
Local absolute and path-like model labels are reduced to filenames before export. Hugging Face refs are preserved. GPU stable IDs and node IDs are exported as stable pseudonymous hashes, not raw identifiers. Route-attempt metrics label local, remote, and endpoint target kinds; remote target IDs are exported only as stable hashes so collectors can aggregate node-to-node traffic without exposing raw peer IDs.
Telemetry attributes are intentionally allowlisted in code. Any new exported attribute must update the allowlist, tests, and this document before it is added to an OTLP record.
| Attribute | Used by | Privacy handling |
|---|---|---|
mesh_llm.model |
lifecycle, request, route | Local/path-like labels are reduced to filenames; Hugging Face refs are preserved. |
mesh_llm.launch_kind |
lifecycle | Bounded enum. |
mesh_llm.gpu_count |
lifecycle | Count only. |
mesh_llm.is_soc |
lifecycle | Boolean only. |
mesh_llm.service_version |
lifecycle, request, route, in-flight | Build version only. |
mesh_llm.architecture |
lifecycle | GGUF architecture string when available. |
mesh_llm.quantization |
lifecycle | Derived quantization label. |
mesh_llm.gpu_name |
lifecycle | Hardware product label; no hostname or stable device ID. |
mesh_llm.gpu_stable_id |
lifecycle | Stable pseudonymous hash of the GPU ID. |
mesh_llm.backend_device |
lifecycle | Backend-local slot label such as CUDA0, ROCm0, Vulkan0, or MTL0. |
mesh_llm.backend |
lifecycle | Runtime/backend label. |
mesh_llm.context_bucket |
lifecycle | Bucketed context length, not the exact configured value. |
mesh_llm.failure_reason |
lifecycle | Bounded enum. |
mesh_llm.source_node_role |
request, route, in-flight | Bounded node role label such as client or worker. |
mesh_llm.source_node_id |
request, route, in-flight | Stable pseudonymous hash of the source node ID. |
mesh_llm.route_service |
request | Bounded service label: local, remote, endpoint, or unavailable. |
mesh_llm.request_outcome |
request | Bounded enum. |
mesh_llm.route_attempt_bucket |
request | Bounded retry bucket: 1, 2, 3_4, or 5_plus. |
mesh_llm.target_kind |
route | Bounded target kind: local, remote, or endpoint. |
mesh_llm.target_node_id |
route | Stable pseudonymous hash for local/remote node targets; omitted for endpoint targets. |
mesh_llm.attempt_outcome |
route | Bounded enum. |
mesh_llm.guardrail.mode |
guardrail decision, guardrail outcome | Bounded enum: disabled, metrics, or enforce. |
mesh_llm.guardrail.contract |
guardrail decision, guardrail outcome | Bounded enum: tools or structured. |
mesh_llm.guardrail.decision |
guardrail decision | Bounded enum: eligible, bypassed, unsupported, or rejected. |
mesh_llm.guardrail.bypass_reason |
guardrail decision | Bounded enum: disabled, streaming, no_contract, unsupported_surface, reserved_collision, or mixed_tools_structured. Omitted when no bypass reason applies. |
mesh_llm.guardrail.outcome |
guardrail outcome | Bounded enum: pass_through, valid, rescued, retried, failed, or metrics_only_failure. |
mesh_llm.guardrail.parser_stage |
guardrail outcome | Bounded enum: none, json_exact, json_fenced, or json_substring. |
mesh_llm.guardrail.attempt_bucket |
guardrail outcome | Bounded retry bucket: 1, 2, or 3_plus. |
llama_stage.verify_window.direct_return_upstream_opened |
Skippy decode summary | Boolean indicating that the preferred upstream-opened v10 prediction-return sink completed its handshake. |
llama_stage.verify_window.direct_return_reverse_fallback |
Skippy decode summary | Boolean indicating that the final stage used the bounded reverse-open v10 prediction-return fallback after the preferred sink was unavailable. |
Review Checklist
Before adding, renaming, or removing OTLP metrics or attributes:
- Run the repo-local telemetry privacy review skill:
.agents/skills/telemetry-privacy-review/SKILL.md. - Keep export destination behavior explicit: no default collector and no ambient
OTel env export unless
telemetry.enabled = true. - Update
TELEMETRY_ATTRIBUTE_ALLOWLISTincrates/mesh-llm-host-runtime/src/runtime/survey.rs. - Update the attribute inventory above.
- Add or update focused tests proving private paths, raw node IDs, raw GPU stable IDs, endpoint URLs, prompts, and completions are not exported.
- Keep guardrail corpus evidence under
.sisyphus/evidence/, separate from OTLP export and from the telemetry metric payloads themselves.
Runtime Safety
Telemetry exporter setup failures disable telemetry without failing inference startup. Runtime events are buffered through a bounded queue; when the queue is full, the oldest event is dropped instead of blocking inference.