1use std::time::Instant;
2
3use crate::codec::{create_codec, CompressedBlock};
4use crate::digest_compat::Digest;
5use crate::error::{PolyKvError, Result};
6use crate::ids_compat::AgentId;
7use crate::manifest::ShellManifest;
8use crate::policy::{CODEC_FIB_K4_N32, CODEC_TURBO_8BIT};
9use crate::pool::SharedKVPool;
10use crate::receipt::{now_unix, CompressedAttentionSelectionReceipt, ShellMaterializeReceipt};
11
12#[derive(Debug, Clone)]
14pub struct ShellLayer {
15 pub layer_index: u32,
17 pub key_blocks: Vec<CompressedBlock>,
19 pub value_blocks: Vec<CompressedBlock>,
21 pub block_digest: Digest,
23}
24
25#[derive(Debug, Clone)]
31pub struct AgentShell {
32 pub agent_id: AgentId,
34 pub shell_manifest: ShellManifest,
36 pub unique_layers: Vec<ShellLayer>,
38 pub pool_digest: Digest,
40 pub build_seed: u64,
42}
43
44impl AgentShell {
45 #[cfg(feature = "fib")]
52 pub fn attention_topk(
53 &self,
54 pool: &SharedKVPool,
55 layer_idx: usize,
56 query: &[f32],
57 top_k: usize,
58 turbo_config: &crate::policy::TurboConfig,
59 ) -> Result<Vec<AttentionHit>> {
60 let decomposed = pool.decompress_layer(layer_idx)?;
61 let head_dim = decomposed.head_dim;
62 let pool_keys = decomposed
63 .keys
64 .first()
65 .ok_or_else(|| PolyKvError::Internal("no pool key head".into()))?;
66 let num_pool_tokens = pool_keys.len() / head_dim;
67
68 let shell_layer = self
70 .unique_layers
71 .iter()
72 .find(|l| l.layer_index == layer_idx as u32)
73 .ok_or_else(|| PolyKvError::Internal(format!("no shell layer {layer_idx}")))?;
74
75 let turbo_codec = create_codec(
76 crate::policy::CODEC_TURBO_8BIT,
77 head_dim,
78 None,
79 Some(turbo_config),
80 )?;
81
82 let mut shell_keys: Vec<f32> = Vec::new();
83 for block in &shell_layer.key_blocks {
84 let decoded = turbo_codec.decode(&block.encoded_payload, self.build_seed)?;
85 shell_keys.extend_from_slice(&decoded);
86 }
87 let num_shell_tokens = shell_keys.len() / head_dim;
88
89 let total = num_pool_tokens + num_shell_tokens;
91 let mut scored: Vec<(usize, f32, bool)> = Vec::with_capacity(total);
92 for i in 0..num_pool_tokens {
93 let start = i * head_dim;
94 let dot: f32 = query
95 .iter()
96 .zip(&pool_keys[start..start + head_dim])
97 .map(|(a, b)| a * b)
98 .sum();
99 scored.push((i, dot, false)); }
101 for i in 0..num_shell_tokens {
102 let start = i * head_dim;
103 let dot: f32 = query
104 .iter()
105 .zip(&shell_keys[start..start + head_dim])
106 .map(|(a, b)| a * b)
107 .sum();
108 scored.push((i, dot, true)); }
110
111 scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
112 scored.truncate(top_k.min(total));
113
114 Ok(scored
115 .into_iter()
116 .map(|(idx, score, from_shell)| AttentionHit {
117 token_index: idx,
118 score,
119 from_shell,
120 })
121 .collect())
122 }
123
124 #[cfg(all(feature = "fib", feature = "turbo"))]
132 pub fn attention_topk_compressed(
133 &self,
134 pool: &SharedKVPool,
135 layer_idx: usize,
136 head_idx: usize,
137 query: &[f32],
138 top_k: usize,
139 ) -> Result<CompressedShellAttentionSelection> {
140 if self.pool_digest != pool.manifest.pool_id {
141 return Err(PolyKvError::DigestMismatch {
142 expected: self.pool_digest.hex().to_string(),
143 got: pool.manifest.pool_id.hex().to_string(),
144 });
145 }
146 if layer_idx >= pool.layers.len() {
147 return Err(PolyKvError::LayerIndexOutOfBounds {
148 index: layer_idx as u32,
149 total: pool.layers.len() as u32,
150 });
151 }
152 let head_dim = pool.manifest.shape.head_dim;
153 if query.len() != head_dim {
154 return Err(PolyKvError::DimensionMismatch {
155 expected: head_dim,
156 got: query.len(),
157 });
158 }
159 let num_heads = pool.manifest.shape.num_kv_heads as usize;
160 if head_idx >= num_heads {
161 return Err(PolyKvError::Internal(format!(
162 "head_idx {head_idx} out of range (have {num_heads})"
163 )));
164 }
165 if pool.manifest.shared_codec != CODEC_FIB_K4_N32 {
166 return Err(PolyKvError::InvalidPolicy(format!(
167 "compressed cold-pool attention requires shared codec {CODEC_FIB_K4_N32}, got {}",
168 pool.manifest.shared_codec
169 )));
170 }
171
172 let pool_layer = &pool.layers[layer_idx];
173 if pool_layer.key_blocks.len() != pool_layer.value_blocks.len() {
174 return Err(PolyKvError::Internal(format!(
175 "layer {layer_idx}: pool key/value block count mismatch ({} vs {})",
176 pool_layer.key_blocks.len(),
177 pool_layer.value_blocks.len()
178 )));
179 }
180 let pool_tokens = pool.manifest.num_shared_tokens as usize;
181 let expected_pool_codes = pool_tokens * num_heads;
182 let fib_adapter = crate::codec::FibQuantAdapter::new(
183 head_dim,
184 pool.manifest.policy.fib_config.k,
185 pool.manifest.policy.fib_config.n,
186 pool.manifest.policy.fib_config.training_samples,
187 pool.manifest.policy.fib_config.lloyd_restarts,
188 pool.manifest.policy.fib_config.lloyd_iterations,
189 )?;
190 let pool_seed = pool.manifest.build_seed;
191 let mut pool_key_codes = Vec::with_capacity(expected_pool_codes);
192 for block in &pool_layer.key_blocks {
193 pool_key_codes
194 .extend(fib_adapter.decode_codes_payload(&block.encoded_payload, pool_seed)?);
195 }
196 let mut pool_value_codes = Vec::with_capacity(expected_pool_codes);
197 for block in &pool_layer.value_blocks {
198 pool_value_codes
199 .extend(fib_adapter.decode_codes_payload(&block.encoded_payload, pool_seed)?);
200 }
201 if pool_key_codes.len() != expected_pool_codes
202 || pool_value_codes.len() != expected_pool_codes
203 {
204 return Err(PolyKvError::Internal(format!(
205 "layer {layer_idx}: decoded {} pool key codes / {} pool value codes, expected {expected_pool_codes}",
206 pool_key_codes.len(), pool_value_codes.len()
207 )));
208 }
209 let fib_quantizer = fib_adapter.build_quantizer(pool_seed)?;
210 let fib_scorer = fib_quant::FibScorer::new(fib_quantizer).map_err(|e| {
211 PolyKvError::Internal(format!("fib compressed scorer construction failed: {e}"))
212 })?;
213 let fib_prepared = fib_scorer.prepare_query(query).map_err(|e| {
214 PolyKvError::Internal(format!("fib compressed query preparation failed: {e}"))
215 })?;
216
217 let shell_layer = self
218 .unique_layers
219 .iter()
220 .find(|l| l.layer_index == layer_idx as u32)
221 .ok_or_else(|| PolyKvError::Internal(format!("no shell layer {layer_idx}")))?;
222 if shell_layer.key_blocks.len() != shell_layer.value_blocks.len() {
223 return Err(PolyKvError::Internal(format!(
224 "layer {layer_idx}: shell key/value block count mismatch ({} vs {})",
225 shell_layer.key_blocks.len(),
226 shell_layer.value_blocks.len()
227 )));
228 }
229 let shell_tokens = shell_layer.key_blocks.len() / num_heads;
230 if shell_layer.key_blocks.len() != shell_tokens * num_heads {
231 return Err(PolyKvError::Internal(format!(
232 "layer {layer_idx}: shell block count {} is not divisible by num_heads {num_heads}",
233 shell_layer.key_blocks.len()
234 )));
235 }
236 let turbo_quantizer = turbo_quant::TurboQuantizer::new(
237 head_dim,
238 pool.policy.turbo_config.bits,
239 pool.policy.turbo_config.projections,
240 self.build_seed,
241 )
242 .map_err(|e| PolyKvError::Internal(format!("turbo quantizer init failed: {e}")))?;
243 let turbo_prepared = turbo_quantizer
244 .prepare_query(query)
245 .map_err(|e| PolyKvError::Internal(format!("turbo query preparation failed: {e}")))?;
246 let turbo_value_codec = create_codec(
247 CODEC_TURBO_8BIT,
248 head_dim,
249 None,
250 Some(&pool.policy.turbo_config),
251 )?;
252
253 let mut scored: Vec<CompressedShellCandidate> =
254 Vec::with_capacity(pool_tokens + shell_tokens);
255 for token_idx in 0..pool_tokens {
256 let code_idx = token_idx * num_heads + head_idx;
257 let score = fib_scorer
258 .score_prepared(&fib_prepared, &pool_key_codes[code_idx])
259 .map_err(|e| PolyKvError::Internal(format!("fib compressed score failed: {e}")))?;
260 scored.push(CompressedShellCandidate {
261 token_index: token_idx,
262 code_index: code_idx,
263 score,
264 from_shell: false,
265 });
266 }
267 for token_idx in 0..shell_tokens {
268 let code_idx = token_idx * num_heads + head_idx;
269 let payload = &shell_layer.key_blocks[code_idx].encoded_payload;
270 let code = decode_turbo_code_payload(payload, &turbo_quantizer)?;
271 let score = turbo_quantizer
272 .inner_product_estimate_prepared(&code, &turbo_prepared)
273 .map_err(|e| {
274 PolyKvError::Internal(format!("turbo compressed score failed: {e}"))
275 })?;
276 scored.push(CompressedShellCandidate {
277 token_index: token_idx,
278 code_index: code_idx,
279 score,
280 from_shell: true,
281 });
282 }
283
284 let selected = top_k.min(scored.len());
285 if selected > 0 && selected < scored.len() {
286 scored.select_nth_unstable_by(selected - 1, |a, b| {
287 b.score
288 .total_cmp(&a.score)
289 .then_with(|| a.from_shell.cmp(&b.from_shell))
290 .then_with(|| a.token_index.cmp(&b.token_index))
291 });
292 scored.truncate(selected);
293 }
294 scored.sort_by(|a, b| {
295 b.score
296 .total_cmp(&a.score)
297 .then_with(|| a.from_shell.cmp(&b.from_shell))
298 .then_with(|| a.token_index.cmp(&b.token_index))
299 });
300 let mut hits = Vec::with_capacity(selected);
301 let mut selected_pool_count = 0u32;
302 let mut selected_shell_count = 0u32;
303 for cand in scored.iter().take(selected) {
304 let value = if cand.from_shell {
305 selected_shell_count += 1;
306 turbo_value_codec.decode(
307 &shell_layer.value_blocks[cand.code_index].encoded_payload,
308 self.build_seed,
309 )?
310 } else {
311 selected_pool_count += 1;
312 fib_scorer
313 .quantizer()
314 .decode(&pool_value_codes[cand.code_index])
315 .map_err(|e| {
316 PolyKvError::DecompressionFailed(format!(
317 "fib selected pool value decode failed: {e}"
318 ))
319 })?
320 };
321 if value.len() != head_dim {
322 return Err(PolyKvError::DimensionMismatch {
323 expected: head_dim,
324 got: value.len(),
325 });
326 }
327 hits.push(CompressedShellAttentionHit {
328 token_index: cand.token_index,
329 score: cand.score,
330 from_shell: cand.from_shell,
331 value,
332 });
333 }
334
335 let shell_digest = self.shell_manifest.digest()?;
336 let receipt = CompressedAttentionSelectionReceipt::new(
337 pool.manifest.pool_id.clone(),
338 layer_idx as u32,
339 head_idx as u32,
340 (pool_tokens + shell_tokens) as u32,
341 hits.len() as u32,
342 (pool_tokens + shell_tokens) as u64,
343 hits.len() as u64,
344 false,
345 "fib_pool_and_turbo_shell_compressed_score_topk_value_decode",
346 format!("{}+{}", pool.manifest.shared_codec, CODEC_TURBO_8BIT),
347 now_unix(),
348 )
349 .with_shell_source_counts(
350 self.agent_id.to_string(),
351 shell_digest,
352 pool_tokens as u32,
353 shell_tokens as u32,
354 selected_pool_count,
355 selected_shell_count,
356 );
357 receipt.validate()?;
358 Ok(CompressedShellAttentionSelection { hits, receipt })
359 }
360
361 #[cfg(not(all(feature = "fib", feature = "turbo")))]
362 pub fn attention_topk_compressed(
363 &self,
364 pool: &SharedKVPool,
365 layer_idx: usize,
366 head_idx: usize,
367 query: &[f32],
368 top_k: usize,
369 ) -> Result<CompressedShellAttentionSelection> {
370 let _ = (pool, layer_idx, head_idx, query, top_k);
371 Err(PolyKvError::CodecUnavailable {
372 codec: "fib+turbo".into(),
373 feature: "fib,turbo".into(),
374 })
375 }
376}
377
378#[derive(Debug, Clone)]
381pub struct AttentionHit {
382 pub token_index: usize,
384 pub score: f32,
386 pub from_shell: bool,
388}
389
390#[derive(Debug, Clone, PartialEq)]
392pub struct CompressedShellAttentionHit {
393 pub token_index: usize,
395 pub score: f32,
397 pub from_shell: bool,
399 pub value: Vec<f32>,
401}
402
403#[derive(Debug, Clone, PartialEq)]
405pub struct CompressedShellAttentionSelection {
406 pub hits: Vec<CompressedShellAttentionHit>,
408 pub receipt: CompressedAttentionSelectionReceipt,
410}
411
412#[derive(Debug, Clone)]
413struct CompressedShellCandidate {
414 token_index: usize,
415 code_index: usize,
416 score: f32,
417 from_shell: bool,
418}
419
420#[cfg(feature = "turbo")]
421fn decode_turbo_code_payload(
422 payload: &[u8],
423 quantizer: &turbo_quant::TurboQuantizer,
424) -> Result<turbo_quant::TurboCode> {
425 if payload.len() >= 4 && &payload[0..4] == turbo_quant::TURBO_CODE_WIRE_MAGIC {
426 turbo_quant::TurboCodeWireV1::decode(payload, quantizer)
427 .map_err(|e| PolyKvError::DecompressionFailed(format!("turbo wire decode failed: {e}")))
428 } else {
429 serde_json::from_slice(payload).map_err(|e| {
430 PolyKvError::DecompressionFailed(format!("turbo code deserialize failed: {e}"))
431 })
432 }
433}
434
435impl ShellLayer {
436 fn compute_digest(&self) -> Result<Digest> {
438 let key_digests: Vec<&str> = self
439 .key_blocks
440 .iter()
441 .map(|b| b.payload_digest.hex())
442 .collect();
443 let value_digests: Vec<&str> = self
444 .value_blocks
445 .iter()
446 .map(|b| b.payload_digest.hex())
447 .collect();
448 let payload = serde_json::json!({
449 "layer_index": self.layer_index,
450 "key_digests": key_digests,
451 "value_digests": value_digests,
452 });
453 crate::digest_compat::compute_json(&payload)
454 }
455}
456
457pub fn materialize_shell(
462 pool: &SharedKVPool,
463 agent_id: &str,
464 agent_tokens: &[(String, Vec<f32>)],
465 seed: u64,
466) -> Result<(AgentShell, ShellMaterializeReceipt)> {
467 let start = Instant::now();
468 let agent_id = AgentId::new(agent_id);
469
470 if agent_tokens.is_empty() {
471 let shell_digest = crate::digest_compat::compute_str(&format!(
473 "empty_shell:{}:{}",
474 agent_id,
475 pool.manifest.pool_id.hex()
476 ));
477
478 let shell_manifest = ShellManifest::new(
479 agent_id.clone(),
480 pool.manifest.pool_id.clone(),
481 0,
482 pool.manifest.num_layers,
483 0,
484 seed,
485 now_unix(),
486 )?;
487
488 let receipt = ShellMaterializeReceipt::new(
489 agent_id.clone(),
490 pool.manifest.pool_id.clone(),
491 shell_digest.clone(),
492 0,
493 0,
494 start.elapsed().as_millis() as u64,
495 now_unix(),
496 );
497
498 return Ok((
499 AgentShell {
500 agent_id,
501 shell_manifest,
502 unique_layers: Vec::new(),
503 pool_digest: pool.manifest.pool_id.clone(),
504 build_seed: seed,
505 },
506 receipt,
507 ));
508 }
509
510 let num_layers = pool.manifest.num_layers as usize;
511 let num_kv_heads = pool.manifest.shape.num_kv_heads as usize;
512 let head_dim = pool.manifest.shape.head_dim;
513
514 let shell_codec = create_codec(
516 CODEC_TURBO_8BIT,
517 head_dim,
518 None,
519 Some(&pool.policy.turbo_config),
520 )?;
521
522 let expected_len = num_layers * num_kv_heads * head_dim * 2;
524 for (token_id, vec) in agent_tokens {
525 if vec.len() != expected_len {
526 return Err(PolyKvError::DimensionMismatch {
527 expected: expected_len,
528 got: vec.len(),
529 });
530 }
531 if vec.iter().any(|v| !v.is_finite()) {
532 return Err(PolyKvError::CorruptPayload(format!(
533 "agent token {} contains non-finite values",
534 token_id
535 )));
536 }
537 }
538
539 let mut unique_layers: Vec<ShellLayer> = Vec::with_capacity(num_layers);
540 let mut total_shell_bytes: u64 = 0;
541 let num_unique_tokens = agent_tokens.len() as u32;
542
543 for layer_idx in 0..num_layers {
544 let mut key_blocks: Vec<CompressedBlock> =
545 Vec::with_capacity(num_unique_tokens as usize * num_kv_heads);
546 let mut value_blocks: Vec<CompressedBlock> =
547 Vec::with_capacity(num_unique_tokens as usize * num_kv_heads);
548
549 for (_token_id, vec) in agent_tokens.iter() {
550 for head_idx in 0..num_kv_heads {
551 let base_offset = layer_idx * num_kv_heads * head_dim * 2 + head_idx * head_dim * 2;
552
553 let key_start = base_offset;
554 let key_end = key_start + head_dim;
555 let key: Vec<f32> = vec[key_start..key_end].to_vec();
556
557 let value_start = key_end;
558 let value_end = value_start + head_dim;
559 let value: Vec<f32> = vec[value_start..value_end].to_vec();
560
561 let encoded_key = shell_codec.encode(&key, seed)?;
562 let encoded_value = shell_codec.encode(&value, seed)?;
563
564 key_blocks.push(CompressedBlock::new(
565 shell_codec.codec_id(),
566 encoded_key,
567 head_dim,
568 ));
569 value_blocks.push(CompressedBlock::new(
570 shell_codec.codec_id(),
571 encoded_value,
572 head_dim,
573 ));
574
575 total_shell_bytes += key_blocks.last().unwrap().compressed_bytes as u64;
576 total_shell_bytes += value_blocks.last().unwrap().compressed_bytes as u64;
577 }
578 }
579
580 let mut layer = ShellLayer {
581 layer_index: layer_idx as u32,
582 key_blocks,
583 value_blocks,
584 block_digest: Digest::from_hex_unchecked(""),
585 };
586 layer.block_digest = layer.compute_digest()?;
587 unique_layers.push(layer);
588 }
589
590 let materialize_ms = start.elapsed().as_millis() as u64;
591 let materialized_at_unix = now_unix();
592
593 let layer_digests: Vec<Digest> = unique_layers
595 .iter()
596 .map(|l| l.block_digest.clone())
597 .collect();
598 let shell_digest_input = serde_json::json!({
599 "agent_id": agent_id,
600 "pool_digest": pool.manifest.pool_id.hex(),
601 "layer_digests": layer_digests.iter().map(|d| d.hex()).collect::<Vec<_>>(),
602 "seed": seed,
603 });
604 let shell_digest = crate::digest_compat::compute_json(&shell_digest_input)?;
605
606 let shell_manifest = ShellManifest::new(
607 agent_id.clone(),
608 pool.manifest.pool_id.clone(),
609 num_unique_tokens,
610 num_layers as u32,
611 total_shell_bytes,
612 seed,
613 materialized_at_unix,
614 )?;
615
616 let receipt = ShellMaterializeReceipt::new(
617 agent_id.clone(),
618 pool.manifest.pool_id.clone(),
619 shell_digest.clone(),
620 num_unique_tokens,
621 total_shell_bytes,
622 materialize_ms,
623 materialized_at_unix,
624 );
625
626 Ok((
627 AgentShell {
628 agent_id,
629 shell_manifest,
630 unique_layers,
631 pool_digest: pool.manifest.pool_id.clone(),
632 build_seed: seed,
633 },
634 receipt,
635 ))
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641 use crate::pool::SharedKVPool;
642 use crate::shape::{AttentionType, KvTensorShape};
643
644 fn make_test_shape() -> KvTensorShape {
645 KvTensorShape {
646 attention_type: AttentionType::MHA,
647 num_layers: 2,
648 num_heads: 4,
649 num_kv_heads: 4,
650 head_dim: 8,
651 hidden_size: 32,
652 }
653 }
654
655 fn make_test_corpus(n: usize) -> Vec<(String, Vec<f32>)> {
656 use rand::Rng;
657 use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
658 let mut rng = ChaCha8Rng::seed_from_u64(42);
659 let shape = make_test_shape();
660 let vec_len = shape.num_layers as usize * shape.num_kv_heads as usize * shape.head_dim * 2;
661
662 (0..n)
663 .map(|i| {
664 let vec: Vec<f32> = (0..vec_len).map(|_| rng.gen_range(-1.0..1.0)).collect();
665 (format!("token_{}", i), vec)
666 })
667 .collect()
668 }
669
670 #[test]
671 fn test_shell_materialize_empty() {
672 let shape = make_test_shape();
673 let corpus = make_test_corpus(4);
674 let (pool, _receipt) = SharedKVPool::build(&corpus, &shape, 42).unwrap();
675
676 let agent_tokens: Vec<(String, Vec<f32>)> = vec![];
677 let (shell, mat_receipt) = materialize_shell(&pool, "agent_1", &agent_tokens, 42).unwrap();
678
679 assert_eq!(shell.agent_id, AgentId::new("agent_1"));
680 assert_eq!(mat_receipt.num_unique_tokens, 0);
681 assert_eq!(mat_receipt.shell_size_bytes, 0);
682 }
683
684 #[test]
685 fn test_shell_materialize_basic() {
686 let shape = make_test_shape();
687 let corpus = make_test_corpus(4);
688 let (pool, _receipt) = SharedKVPool::build(&corpus, &shape, 42).unwrap();
689
690 let agent_tokens = make_test_corpus(2);
691 let (shell, mat_receipt) = materialize_shell(&pool, "agent_x", &agent_tokens, 42).unwrap();
692
693 assert_eq!(shell.agent_id, AgentId::new("agent_x"));
694 assert_eq!(shell.unique_layers.len(), 2);
695 assert_eq!(mat_receipt.num_unique_tokens, 2);
696 assert!(mat_receipt.shell_size_bytes > 0);
697 assert_eq!(shell.pool_digest, pool.manifest.pool_id);
698 }
699
700 #[test]
701 fn test_shell_materialize_deterministic() {
702 let shape = make_test_shape();
703 let corpus = make_test_corpus(4);
704 let (pool, _receipt) = SharedKVPool::build(&corpus, &shape, 42).unwrap();
705 let agent_tokens = make_test_corpus(2);
706
707 let (shell1, receipt1) = materialize_shell(&pool, "agent_x", &agent_tokens, 42).unwrap();
708 let (shell2, receipt2) = materialize_shell(&pool, "agent_x", &agent_tokens, 42).unwrap();
709
710 assert_eq!(receipt1.shell_digest, receipt2.shell_digest);
711 assert_eq!(
712 shell1.unique_layers[0].block_digest,
713 shell2.unique_layers[0].block_digest
714 );
715 }
716
717 #[test]
718 fn test_shell_compressed_attention_topk_scores_pool_and_shell_without_full_decode() {
719 let shape = make_test_shape();
720 let corpus = make_test_corpus(16);
721 let (pool, _receipt) = SharedKVPool::build(&corpus, &shape, 42).unwrap();
722 let agent_tokens = make_test_corpus(3);
723 let (shell, _mat_receipt) = materialize_shell(&pool, "agent_x", &agent_tokens, 42).unwrap();
724 let query: Vec<f32> = (0..shape.head_dim).map(|x| x as f32 * 0.125).collect();
725
726 let out = shell
727 .attention_topk_compressed(&pool, 0, 0, &query, 5)
728 .expect("shell compressed attention selection should work");
729
730 assert_eq!(out.hits.len(), 5);
731 assert!(out.hits.iter().any(|hit| !hit.from_shell));
732 assert!(out.hits.iter().all(|hit| hit.value.len() == shape.head_dim));
733 assert_eq!(out.receipt.candidate_count, 19);
734 assert_eq!(out.receipt.pool_candidate_count, 16);
735 assert_eq!(out.receipt.shell_candidate_count, 3);
736 assert_eq!(out.receipt.selected_count, 5);
737 assert_eq!(
738 out.receipt.selected_pool_count + out.receipt.selected_shell_count,
739 5
740 );
741 assert_eq!(out.receipt.decoded_value_vectors, 5);
742 assert!(!out.receipt.full_layer_decoded);
743 assert!(out.receipt.exact_fallback_required);
744 assert_eq!(out.receipt.agent_id.as_deref(), Some("agent_x"));
745 assert!(out.receipt.shell_digest.is_some());
746 assert_eq!(
747 out.receipt.scoring_path,
748 "fib_pool_and_turbo_shell_compressed_score_topk_value_decode"
749 );
750 assert!(out
751 .receipt
752 .claim_boundary
753 .contains("compressed candidate selection only"));
754 for window in out.hits.windows(2) {
755 assert!(window[0].score >= window[1].score);
756 }
757 }
758
759 #[test]
760 fn test_shell_compressed_attention_rejects_wrong_query_dimension() {
761 let shape = make_test_shape();
762 let corpus = make_test_corpus(8);
763 let (pool, _receipt) = SharedKVPool::build(&corpus, &shape, 42).unwrap();
764 let agent_tokens = make_test_corpus(1);
765 let (shell, _mat_receipt) = materialize_shell(&pool, "agent_x", &agent_tokens, 42).unwrap();
766
767 let err = shell
768 .attention_topk_compressed(&pool, 0, 0, &[1.0, 2.0], 3)
769 .expect_err("wrong query dimension must fail");
770
771 assert!(matches!(
772 err,
773 PolyKvError::DimensionMismatch {
774 expected: 8,
775 got: 2
776 }
777 ));
778 }
779}