Skip to main content

poly_kv/
shell.rs

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/// One layer's worth of agent-unique compressed KV blocks.
13#[derive(Debug, Clone)]
14pub struct ShellLayer {
15    /// Zero-based layer index.
16    pub layer_index: u32,
17    /// Key blocks unique to this agent (turbo-quant compressed).
18    pub key_blocks: Vec<CompressedBlock>,
19    /// Value blocks unique to this agent (turbo-quant compressed).
20    pub value_blocks: Vec<CompressedBlock>,
21    /// Blake3 digest of all blocks in this layer.
22    pub block_digest: Digest,
23}
24
25/// A per-agent compressed context shell.
26///
27/// AgentShell stores turbo-quant compressed KV vectors for tokens that are
28/// unique to a specific agent. Shared tokens are referenced from the parent
29/// pool by digest, not duplicated.
30#[derive(Debug, Clone)]
31pub struct AgentShell {
32    /// Agent identifier.
33    pub agent_id: AgentId,
34    /// Shell manifest.
35    pub shell_manifest: ShellManifest,
36    /// Per-layer unique token blocks (turbo-quant compressed).
37    pub unique_layers: Vec<ShellLayer>,
38    /// Reference to the parent pool digest.
39    pub pool_digest: Digest,
40    /// Seed used for deterministic codec operations.
41    pub build_seed: u64,
42}
43
44impl AgentShell {
45    /// Compute attention over pool + shell for a given query.
46    ///
47    /// Decompresses the shared pool keys and the shell's unique keys for
48    /// the specified layer, concatenates them, and returns the top-K
49    /// token indices with their dot-product scores. Returns a marker
50    /// indicating whether each hit came from the pool or the shell.
51    #[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        // Decode shell keys for this layer
69        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        // Score all tokens (pool + shell)
90        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)); // false = pool
100        }
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)); // true = shell
109        }
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    /// Compute global top-k attention over the compressed cold pool and this
125    /// agent's compressed hot shell without full-layer decode.
126    ///
127    /// The method scores cold-pool Fib codes and hot-shell Turbo codes in their
128    /// compressed forms, selects global top-k candidates, and decodes only the
129    /// selected value vectors. The receipt is candidate-selection evidence only;
130    /// model-quality claims still require exact attention/logit/PPL replay.
131    #[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/// A single attention hit identifying whether the token came from the
379/// shared pool or the agent shell.
380#[derive(Debug, Clone)]
381pub struct AttentionHit {
382    /// Token index within its source (pool or shell).
383    pub token_index: usize,
384    /// Dot-product score with the query.
385    pub score: f32,
386    /// True if this token is from the agent shell, false if from the pool.
387    pub from_shell: bool,
388}
389
390/// One selected compressed-attention hit across cold pool + hot shell.
391#[derive(Debug, Clone, PartialEq)]
392pub struct CompressedShellAttentionHit {
393    /// Token index within its source (pool or shell).
394    pub token_index: usize,
395    /// Compressed-domain approximate score.
396    pub score: f32,
397    /// True if this token is from the agent shell, false if from the pool.
398    pub from_shell: bool,
399    /// Decoded value vector for selected candidates only.
400    pub value: Vec<f32>,
401}
402
403/// Output of compressed-domain top-k selection over cold pool + hot shell.
404#[derive(Debug, Clone, PartialEq)]
405pub struct CompressedShellAttentionSelection {
406    /// Selected hits sorted by descending compressed-domain score.
407    pub hits: Vec<CompressedShellAttentionHit>,
408    /// Candidate-selection receipt with source counts and claim boundary.
409    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    /// Compute a content digest over the blocks in this layer.
437    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
457/// Materialize an AgentShell from a SharedKVPool and agent-specific tokens.
458///
459/// Agent tokens that are not found in the shared pool are compressed with
460/// turbo-quant and stored in shell layers.
461pub 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        // Empty shell — agent uses only shared pool tokens
472        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    // Create the turbo-quant codec
515    let shell_codec = create_codec(
516        CODEC_TURBO_8BIT,
517        head_dim,
518        None,
519        Some(&pool.policy.turbo_config),
520    )?;
521
522    // Validate agent token shapes
523    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    // Compute shell digest
594    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}