1#[cfg_attr(feature = "serde", derive(serde::Serialize))]
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43#[allow(missing_docs)]
44pub enum WebStack {
45 Spine,
53 OpenAiApi,
56 AnthropicApi,
59 Mcp,
62 Grpc,
65 HttpJson,
68 GraphQl,
71}
72
73impl WebStack {
74 pub fn all() -> [WebStack; 7] {
76 [
77 WebStack::Spine,
78 WebStack::OpenAiApi,
79 WebStack::AnthropicApi,
80 WebStack::Mcp,
81 WebStack::Grpc,
82 WebStack::HttpJson,
83 WebStack::GraphQl,
84 ]
85 }
86
87 pub fn name(self) -> &'static str {
89 match self {
90 WebStack::Spine => "spine",
91 WebStack::OpenAiApi => "openai-api",
92 WebStack::AnthropicApi => "anthropic-api",
93 WebStack::Mcp => "mcp",
94 WebStack::Grpc => "grpc",
95 WebStack::HttpJson => "http-json",
96 WebStack::GraphQl => "graphql",
97 }
98 }
99
100 pub fn from_name(name: &str) -> Option<WebStack> {
103 match name.to_ascii_lowercase().as_str() {
104 "spine" | "nervosys-spine" => Some(WebStack::Spine),
105 "openai" | "openai-api" | "gpt-api" => Some(WebStack::OpenAiApi),
106 "anthropic" | "anthropic-api" | "claude-api" => Some(WebStack::AnthropicApi),
107 "mcp" | "model-context-protocol" => Some(WebStack::Mcp),
108 "grpc" | "g-rpc" => Some(WebStack::Grpc),
109 "http-json" | "rest" | "http+json" | "json-over-http" => Some(WebStack::HttpJson),
110 "graphql" | "graphql-spec" | "gql" => Some(WebStack::GraphQl),
111 _ => None,
112 }
113 }
114}
115
116#[cfg_attr(feature = "serde", derive(serde::Serialize))]
119#[derive(Debug, Clone)]
120pub struct WebStackProfile {
121 pub stack: WebStack,
123 pub streaming: f64,
126 pub tool_discoverability: f64,
129 pub encoding_efficiency: f64,
132 pub interop: f64,
135 pub security_primitives: f64,
138 pub evidence: Vec<&'static str>,
140}
141
142impl WebStackProfile {
143 pub fn fitness(&self) -> f64 {
145 (self.streaming
146 + self.tool_discoverability
147 + self.encoding_efficiency
148 + self.interop
149 + self.security_primitives)
150 / 5.0
151 }
152}
153
154impl std::fmt::Display for WebStackProfile {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 write!(
157 f,
158 "{}: fitness {:.2} (streaming {:.2}, tools {:.2}, encoding {:.2}, interop {:.2}, security {:.2})",
159 self.stack.name(),
160 self.fitness(),
161 self.streaming,
162 self.tool_discoverability,
163 self.encoding_efficiency,
164 self.interop,
165 self.security_primitives
166 )
167 }
168}
169
170pub fn profile(stack: WebStack) -> WebStackProfile {
172 match stack {
173 WebStack::Spine => WebStackProfile {
174 stack,
175 streaming: 0.98,
176 tool_discoverability: 0.95,
177 encoding_efficiency: 0.95,
178 interop: 0.67,
179 security_primitives: 0.95,
180 evidence: vec![
181 "StreamStart / StreamToken { seq, data, usage? } / StreamEnd are first-class Message variants; StreamData carries Text | Bytes | ToolCall | Encoded(EncodedFrame), so latents and mid-stream function calls fall out of the same frame. v1.5.0 adds Message::StreamCancel (cancel one stream by id — SPINE multiplexes many streams per connection, so closing the socket like SSE is too blunt) and optional StreamToken.usage (mid-stream cumulative token budget, the multiplexed analogue of OpenAI stream_options.include_usage). As of v1.9.0 the gRPC StreamChat is backed by a pluggable real model (OpenAiChatModel streams any OpenAI-compatible endpoint) mapped LAZILY, so cancelling the stream actually stops upstream generation — verified by test",
182 "two discovery surfaces: native CapabilityQuery (Exact | Prefix | Semantic { embedding, top_k } | All) → CapabilityAdvertisement with input/output JSON Schema per Capability plus optional embedding for similarity-matched lookup; and, as of v1.5.0, the spine_protocol::mcp bridge that re-exposes the same capabilities over the MCP tools/list / tools/call contract — so SPINE matches the introspection gold standard AND adds semantic capability search MCP lacks",
183 "the wire body is a self-describing binary codec (8-byte SpineWireHeader + CBOR/RFC 8949); EncodedFrame.data and StreamData::Bytes are CBOR byte strings (serde_bytes), giving tensor payloads protobuf-class density. v1.7.0 made the default wire::encode plain CBOR — fast AND dense, no hot-path compression (a benchmark caught the prior zstd-per-frame design costing ~250 µs/frame; plain CBOR encodes a 1 KiB embedding in ~590 ns, ~10× faster than the JSON it replaced). Measured (spine-protocol examples/wire_sizes.rs, header included): the embedding frame is 3975→1263 B (68% smaller) on the fast default and 3975→446 B (89%) via the opt-in wire::encode_compressed; a tool call 323→284 B; every frame beats JSON, and EncodedFrame moves raw f32/f16/bf16/q8/q4 tensor bytes zero-token — a path gRPC has no native equivalent for. At parity with protobuf for the agentic data plane, and now fast as well as small",
184 "measured transport benchmarks back the encoding/streaming scores (nervosys/SPINE src/spine-transport/benches: spine_vs_http2, agentic_ai_workload, llm_tok_per_sec; re-run 2026-06-08 vs the real h2 HTTP/2 crate on one persistent TCP connection). Single-stream: SPINE wins latency 1.6–2.4× and throughput 1.8–2.3×; N=64 pipelined multiplexing ~32× (≈1.3M req/s on one connection). Dominant agentic pattern (batches of 1536-dim embeddings between agents — RAG / fleet broadcast): SPINE beats HTTP/2+JSON ~6–25×. LLM token streaming: SPINE sustains hundreds of millions of tokens/sec (9–15× over HTTP/2+binary at ≥16K-token batches) where OpenAI-style JSON-SSE caps near ~10M tok/s and collapses on large batches. These are TCP-loopback medians — direction and order of magnitude reproduce run-to-run, but absolute peaks are bandwidth/scheduler-bound and machine-dependent; full methodology in BENCHMARK_REPORT.md",
185 "the neural encoder-decoder protocol is itself benchmarked for agentic use (nervosys/SPINE spine-protocol benches/neural_codec_bench.rs, 2026-06-08): the real TitansLatentCodec projects text into a fixed-width Titans latent and frames it as a self-describing EncodedFrame. The resulting on-wire frame is 66–71% smaller than its JSON form (dim 256: 1241 B binary vs 3942 B JSON; dim 1024: 4314 B vs 14803 B) because the latent rides as a CBOR byte string, not a JSON float array — this latent data plane is what the 0.95 encoding score rests on, and gRPC/JSON have no native equivalent. Honest cost: it is a genuine Titans forward pass, not a memcpy, so encode is superlinear in width (~94 µs at dim 128, ~847 µs at 256, ~3.1 ms at 512, ~26 ms at 1024) — the one-time sender-side price of a semantic projection, separate from the wire-size win the score reflects",
186 "still young (nervosys/SPINE), but reachable from three dominant ecosystems via deployable, standards-compliant server bridges: (1) the spine_protocol::mcp runnable MCP server (v1.6.0 — mcp::serve_stdio speaks the stdio JSON-RPC transport, so a Claude Desktop / Code mcpServers entry drives a SPINE agent today); (2) the OpenAI-compatible /v1/chat/completions + /v1/embeddings + /v1/agentic/{capabilities,codecs} gateway; and (3) the spine-grpc crate — a tonic AgentService (ListCapabilities / CallTool / streaming StreamChat, verified end-to-end over real HTTP/2) which as of v1.9.0 is production-grade: gRPC server reflection (grpcurl and any tooling introspect it with zero stubs), a runnable serve example, and a real pluggable model backend (StreamChat streams from any OpenAI-compatible endpoint — including SPINE's own gateway, so the bridges compose). A gRPC or MCP client uses 100% standard stubs — these are real, deployable bridges, not SPINE-specific shims — but each maps the agentic surface (not SPINE's native binary latent frames), and SPINE's own protocol still has ~zero native install base, so interop stays its lowest axis despite the breadth",
187 "message-level security, not just channel: v1.5.0 spine_agentic::signed_frame wraps any frame in an Ed25519 detached signature over the exact wire bytes (integrity + authenticity + non-repudiation, verified before decode) — a guarantee mTLS does not give once a message leaves the socket. Plus W3C TraceContext inline on tool calls/results/stream starts; bearer auth SECURE BY DEFAULT since v1.3.0; zeroize-on-drop on every key-bearing struct; optional FIPS 140-3 build via aws-lc-rs; Chameleon moving-target protocol + Certificate Transparency policy in the box",
188 ],
189 },
190 WebStack::OpenAiApi => WebStackProfile {
191 stack,
192 streaming: 0.85,
193 tool_discoverability: 0.70,
194 encoding_efficiency: 0.35,
195 interop: 1.00,
196 security_primitives: 0.55,
197 evidence: vec![
198 "SSE chat.completion.chunk is the de facto wire format for LLM tokens; clients consume `data: {...}\\ndata: [DONE]` natively; delta.content + delta.tool_calls handle text and function-call deltas in the same chunk shape",
199 "`tools` parameter on the request declares available functions with JSON Schema args — the agent can branch on returned tool_calls — but there is no `tools/list` introspection RPC; discovery is request-time, not protocol-time",
200 "JSON over HTTP/1.1 or HTTP/2 — the verbose baseline; no first-class binary or latent path",
201 "every major SDK speaks it; LangChain, LlamaIndex, Vercel AI SDK, all OSS agent frameworks default to the OpenAI shape, and most competing providers (Azure, Together, Groq, Fireworks, OpenRouter, …) expose an OpenAI-compatible endpoint as their first interface — the dominant network effect",
202 "bearer token + TLS; per-message identity / tracing / integrity are someone-else's problem (use HTTP headers and your own observability stack)",
203 ],
204 },
205 WebStack::AnthropicApi => WebStackProfile {
206 stack,
207 streaming: 0.85,
208 tool_discoverability: 0.70,
209 encoding_efficiency: 0.35,
210 interop: 0.85,
211 security_primitives: 0.55,
212 evidence: vec![
213 "SSE message_start / content_block_start / content_block_delta / message_delta / message_stop is the streaming protocol; carries text deltas and tool_use blocks; clients consume it the same way they consume OpenAI SSE",
214 "`tools` parameter on the request declares tool surface with JSON Schema; tool_use / tool_result blocks complete the loop; no protocol-level tools/list introspection",
215 "JSON over HTTPS — same shape as the OpenAI baseline",
216 "wide SDK + framework coverage; second-largest closed-LLM ecosystem; some clients reach it through the OpenAI-compatible adapter layer rather than natively",
217 "bearer token + TLS; computer use / agent skills add capability surface but auth/tracing/integrity primitives remain transport-level",
218 ],
219 },
220 WebStack::Mcp => WebStackProfile {
221 stack,
222 streaming: 0.40,
223 tool_discoverability: 0.95,
224 encoding_efficiency: 0.40,
225 interop: 0.65,
226 security_primitives: 0.40,
227 evidence: vec![
228 "JSON-RPC notifications carry tool progress / log entries / sampling — not LLM-token-native; streaming is generic notification flow rather than chat.completion.chunk-shaped",
229 "`tools/list`, `resources/list`, `prompts/list`, `tools/call`, `resources/read` are the protocol — discoverability IS the design center; this is the highest-scoring axis for any stack here",
230 "JSON-RPC text envelopes over stdio or SSE — verbose like JSON-over-HTTP but with the JSON-RPC frame overhead on top",
231 "Anthropic-published in late 2024, adopted by Claude Desktop, Claude Code, several IDE integrations, and growing through 2025-2026; the de facto tool-server contract for agent runtimes",
232 "transport-level (stdio process boundary or HTTPS for SSE); no in-protocol auth/tracing/integrity — relies on the host process or HTTP layer",
233 ],
234 },
235 WebStack::Grpc => WebStackProfile {
236 stack,
237 streaming: 0.70,
238 tool_discoverability: 0.85,
239 encoding_efficiency: 0.95,
240 interop: 0.85,
241 security_primitives: 0.80,
242 evidence: vec![
243 "first-class server / client / bidirectional streaming over HTTP/2 — strong general streaming, but no LLM-token shape out of the box; an agent service has to define its own chunk schema",
244 "Server Reflection (proto-reflect) exposes service / method / message descriptors; introspection works but the agent must translate proto types — less direct than tools/list",
245 "protobuf binary on HTTP/2 framing — the most compact mainstream wire format; zero JSON envelope overhead",
246 "huge enterprise install base; standard for high-throughput internal services and machine-to-machine traffic; well represented in agent backends even if not at the LLM edge",
247 "mTLS first-class, per-channel interceptors for auth and OpenTelemetry tracing, deadlines propagate on the wire — among the strongest protocol-level security surfaces in the set",
248 ],
249 },
250 WebStack::HttpJson => WebStackProfile {
251 stack,
252 streaming: 0.55,
253 tool_discoverability: 0.40,
254 encoding_efficiency: 0.30,
255 interop: 1.00,
256 security_primitives: 0.45,
257 evidence: vec![
258 "chunked Transfer-Encoding + SSE handle streaming as a bolt-on; HTTP/2 server push and HTTP/3 datagrams help, but there is no LLM-token frame standard",
259 "OpenAPI/Swagger gives schema discoverability when the service ships one — but the protocol itself does not require it; many real services have no schema endpoint",
260 "verbose JSON over HTTP/1.1 (HTTP/2 helps with framing but not body size); the cost baseline every more-efficient stack measures against",
261 "the universal lingua franca of services; every language, every framework, every agent stack can call a JSON HTTP endpoint",
262 "TLS at transport; auth/tracing/integrity are conventions (bearer headers, W3C traceparent, content hashing) layered on, not in the protocol",
263 ],
264 },
265 WebStack::GraphQl => WebStackProfile {
266 stack,
267 streaming: 0.50,
268 tool_discoverability: 0.95,
269 encoding_efficiency: 0.35,
270 interop: 0.75,
271 security_primitives: 0.45,
272 evidence: vec![
273 "Subscriptions handle streaming as a separate operation type over WebSocket / SSE; not LLM-token native; defer / stream directives help for partial results",
274 "introspection (__schema, __type) is built into the protocol — a client can discover the entire surface without docs; on this axis GraphQL ties MCP for the highest score",
275 "JSON request/response with selection sets that reduce over-fetch; binary efficiency is still text-JSON-shaped",
276 "wide adoption especially on the front-end and federated-service edges; persisted-query patterns are common in agent backends",
277 "TLS at transport; persisted queries help control surface area; per-message identity / tracing / integrity remain conventions",
278 ],
279 },
280 }
281}
282
283pub fn profiles() -> Vec<WebStackProfile> {
285 WebStack::all().iter().map(|&s| profile(s)).collect()
286}
287
288pub fn rank_web_stacks() -> Vec<WebStackProfile> {
291 let mut v = profiles();
292 v.sort_by(|a, b| {
293 b.fitness()
294 .partial_cmp(&a.fitness())
295 .unwrap_or(std::cmp::Ordering::Equal)
296 });
297 v
298}
299
300#[cfg_attr(feature = "serde", derive(serde::Serialize))]
302#[derive(Debug, Clone)]
303pub struct WebStackComparison {
304 pub a: WebStackProfile,
306 pub b: WebStackProfile,
308 pub fitness_delta: f64,
310 pub axis_deltas: Vec<(&'static str, f64)>,
312}
313
314pub fn compare_web_stacks(a: WebStack, b: WebStack) -> WebStackComparison {
316 let pa = profile(a);
317 let pb = profile(b);
318 let axis_deltas = vec![
319 ("streaming", pa.streaming - pb.streaming),
320 (
321 "tool-discoverability",
322 pa.tool_discoverability - pb.tool_discoverability,
323 ),
324 (
325 "encoding-efficiency",
326 pa.encoding_efficiency - pb.encoding_efficiency,
327 ),
328 ("interop", pa.interop - pb.interop),
329 (
330 "security-primitives",
331 pa.security_primitives - pb.security_primitives,
332 ),
333 ];
334 WebStackComparison {
335 fitness_delta: pa.fitness() - pb.fitness(),
336 a: pa,
337 b: pb,
338 axis_deltas,
339 }
340}
341
342impl std::fmt::Display for WebStackComparison {
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 writeln!(
345 f,
346 "{} vs {}: fitness delta {:+.2}",
347 self.a.stack.name(),
348 self.b.stack.name(),
349 self.fitness_delta
350 )?;
351 for (axis, d) in &self.axis_deltas {
352 writeln!(f, " {axis}: {d:+.2}")?;
353 }
354 Ok(())
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn every_stack_profiles_with_evidence() {
364 for stack in WebStack::all() {
365 let p = profile(stack);
366 assert!(
367 p.evidence.len() >= 3,
368 "{} needs ≥3 evidence lines",
369 stack.name()
370 );
371 for s in [
372 p.streaming,
373 p.tool_discoverability,
374 p.encoding_efficiency,
375 p.interop,
376 p.security_primitives,
377 ] {
378 assert!(
379 (0.0..=1.0).contains(&s),
380 "{} score out of range",
381 stack.name()
382 );
383 }
384 }
385 }
386
387 #[test]
388 fn from_name_roundtrip_and_aliases() {
389 for stack in WebStack::all() {
390 assert_eq!(WebStack::from_name(stack.name()), Some(stack));
391 }
392 assert_eq!(WebStack::from_name("OpenAI"), Some(WebStack::OpenAiApi));
393 assert_eq!(WebStack::from_name("claude-api"), Some(WebStack::AnthropicApi));
394 assert_eq!(WebStack::from_name("REST"), Some(WebStack::HttpJson));
395 assert_eq!(
396 WebStack::from_name("model-context-protocol"),
397 Some(WebStack::Mcp)
398 );
399 assert_eq!(WebStack::from_name("gql"), Some(WebStack::GraphQl));
400 assert_eq!(WebStack::from_name("nervosys-spine"), Some(WebStack::Spine));
401 assert_eq!(WebStack::from_name("not-a-stack"), None);
402 }
403
404 #[test]
405 fn ranking_is_deterministic_and_sorted() {
406 let r1 = rank_web_stacks();
407 let r2 = rank_web_stacks();
408 let n1: Vec<_> = r1.iter().map(|p| p.stack.name()).collect();
409 let n2: Vec<_> = r2.iter().map(|p| p.stack.name()).collect();
410 assert_eq!(n1, n2);
411 for w in r1.windows(2) {
412 assert!(w[0].fitness() >= w[1].fitness());
413 }
414 }
415
416 #[test]
417 fn axis_judgments_hold_directionally() {
418 let spine = profile(WebStack::Spine);
419 let openai = profile(WebStack::OpenAiApi);
420 let anthropic = profile(WebStack::AnthropicApi);
421 let mcp = profile(WebStack::Mcp);
422 let grpc = profile(WebStack::Grpc);
423 let http = profile(WebStack::HttpJson);
424 let graphql = profile(WebStack::GraphQl);
425
426 assert!(
428 spine.streaming > anthropic.streaming,
429 "native StreamStart/Token/End frames beat SSE-on-HTTP for LLM streaming"
430 );
431 assert!(
432 spine.security_primitives > openai.security_primitives,
433 "inline W3C tracing + zeroize + secure-by-default auth + Chameleon protocol beat bearer-on-TLS"
434 );
435
436 assert!(
438 openai.interop > spine.interop,
439 "the OpenAI API has the dominant ecosystem network effect; SPINE is brand new"
440 );
441 assert!(
442 http.interop >= openai.interop,
443 "plain HTTP+JSON is the universal lingua franca"
444 );
445
446 assert!(
448 grpc.encoding_efficiency > openai.encoding_efficiency,
449 "protobuf binary beats verbose JSON over HTTP"
450 );
451
452 assert!(
455 spine.encoding_efficiency > openai.encoding_efficiency,
456 "binary CBOR (+opportunistic zstd) crushes JSON-over-HTTP"
457 );
458 assert!(
459 spine.encoding_efficiency > mcp.encoding_efficiency,
460 "binary CBOR beats JSON-RPC text envelopes"
461 );
462 assert!(
463 spine.encoding_efficiency >= grpc.encoding_efficiency,
464 "byte-string tensor payloads bring SPINE to parity with protobuf for the agentic data plane"
465 );
466 assert!(
467 grpc.security_primitives > http.security_primitives,
468 "mTLS + interceptors beat bring-your-own bearer-headers"
469 );
470
471 assert!(
477 spine.fitness() > grpc.fitness(),
478 "v1.5.0 capability work puts SPINE first on composite agentic fitness"
479 );
480 assert!(
481 grpc.interop > spine.interop,
482 "SPINE reaches the ecosystem through MCP/OpenAI bridges, not native adoption; gRPC's install base is broader"
483 );
484 assert!(
485 spine.security_primitives > grpc.security_primitives,
486 "per-message Ed25519 signatures (non-repudiation) exceed channel-only mTLS"
487 );
488
489 assert!(
491 mcp.tool_discoverability > openai.tool_discoverability,
492 "tools/list is more discoverable than a request-time tools parameter"
493 );
494 assert!(
495 graphql.tool_discoverability > http.tool_discoverability,
496 "__schema introspection beats hoping the service ships OpenAPI"
497 );
498
499 assert!(
501 openai.streaming > mcp.streaming,
502 "JSON-RPC notifications are not LLM-token streaming"
503 );
504 }
505
506 #[test]
507 fn comparison_deltas_are_consistent() {
508 let cmp = compare_web_stacks(WebStack::Spine, WebStack::OpenAiApi);
509 let sum: f64 = cmp.axis_deltas.iter().map(|(_, d)| d).sum();
510 assert!((sum / 5.0 - cmp.fitness_delta).abs() < 1e-9);
511 assert!(format!("{cmp}").contains("spine vs openai-api"));
512 }
513}