What happens today. #1437 landed lifecycle-hook terminal events for exchanges a node serves, on both the typed frontend seam and the raw-proxy plugin-served path (try_route_plugin_model). One path is not covered: when a node's /v1 frontend routes a request to a peer on the mesh instead of serving it locally (route_missing_local_model's remote-mesh branch), the routing node publishes nothing on openai.exchange.v1.
Verified live on a 3-node mesh (2026-09-04): node A routes a chat completion to node B; B publishes its own terminal envelope and its plugin acts on it correctly; A — the node the client actually talked to — has a byte-for-byte unchanged plugin event log for the whole exchange. A plugin that wants to observe what a node asked for, not only what it served, currently sees nothing on this path. That matters for anything double-entry: billing reconciliation, client-side audit, or a node keeping its own record of requests it initiated.
Proposed change (host). One additional publish call site in crates/mesh-llm-host-runtime/src/network/openai/ingress.rs, mirroring try_route_plugin_model's existing effective/terminal publish pattern 1:1, plus a new OpenAiExchangeDispatchPath::RemoteMesh variant so a downstream plugin can tell "I routed this" from "I served this". Same envelope shape, same fields (exchange_id, model, status); capsule_id/nonce stay absent on this path, same as the existing plugin-served terminal event, since no marker exists here yet. No change to the envelope schema, served-node behaviour, or the plugin ABI.
Acceptance. A node that only routes (never serves) an exchange publishes an effective+terminal pair on openai.exchange.v1 with dispatch_path: RemoteMesh; the served node's own publish is unaffected; existing dispatch-path tests unchanged.
Out of scope here. Reading back the peer's response marker on the routed path so the routing node's event can carry the peer's identity for the exchange — a separate, small follow-on once this lands.
PR: #1668.
What happens today. #1437 landed lifecycle-hook terminal events for exchanges a node serves, on both the typed frontend seam and the raw-proxy plugin-served path (
try_route_plugin_model). One path is not covered: when a node's/v1frontend routes a request to a peer on the mesh instead of serving it locally (route_missing_local_model's remote-mesh branch), the routing node publishes nothing onopenai.exchange.v1.Verified live on a 3-node mesh (2026-09-04): node A routes a chat completion to node B; B publishes its own terminal envelope and its plugin acts on it correctly; A — the node the client actually talked to — has a byte-for-byte unchanged plugin event log for the whole exchange. A plugin that wants to observe what a node asked for, not only what it served, currently sees nothing on this path. That matters for anything double-entry: billing reconciliation, client-side audit, or a node keeping its own record of requests it initiated.
Proposed change (host). One additional publish call site in
crates/mesh-llm-host-runtime/src/network/openai/ingress.rs, mirroringtry_route_plugin_model's existing effective/terminal publish pattern 1:1, plus a newOpenAiExchangeDispatchPath::RemoteMeshvariant so a downstream plugin can tell "I routed this" from "I served this". Same envelope shape, same fields (exchange_id,model,status);capsule_id/nonce stay absent on this path, same as the existing plugin-served terminal event, since no marker exists here yet. No change to the envelope schema, served-node behaviour, or the plugin ABI.Acceptance. A node that only routes (never serves) an exchange publishes an effective+terminal pair on
openai.exchange.v1withdispatch_path: RemoteMesh; the served node's own publish is unaffected; existing dispatch-path tests unchanged.Out of scope here. Reading back the peer's response marker on the routed path so the routing node's event can carry the peer's identity for the exchange — a separate, small follow-on once this lands.
PR: #1668.