lean_ctx/core/context_kernel/
capsule_wire.rs1use std::collections::{HashMap, HashSet};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
9pub struct ContextCapsuleV1 {
10 pub capsule_id: String,
12 pub manifest_ref: String,
14 pub selected_refs: Vec<String>,
16 pub intent: String,
18 pub budget_tokens: u64,
20 pub budget_remaining: u64,
22 pub policy_version: String,
24 pub sensitivity: String,
26 pub owner_agent: String,
28 pub target_agents: Vec<String>,
30 pub parent_capsule: Option<String>,
32 pub created_at_epoch: u64,
34}
35
36#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
39pub struct DeltaTransfer {
40 pub base_ref: String,
42 pub added_refs: Vec<String>,
44 pub removed_refs: Vec<String>,
46 pub budget_delta: i64,
48 pub new_findings: Vec<String>,
50}
51
52#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
54pub struct DedupReport {
55 pub shared_refs: Vec<String>,
57 pub unique_per_capsule: Vec<Vec<String>>,
59 pub dedup_ratio: f64,
61}
62
63impl ContextCapsuleV1 {
64 pub fn new(intent: &str, refs: Vec<String>, budget: u64, owner: &str) -> Self {
66 let capsule_id = capsule_id(intent, &refs);
67 Self {
68 manifest_ref: capsule_id.clone(),
69 capsule_id,
70 selected_refs: refs,
71 intent: intent.to_owned(),
72 budget_tokens: budget,
73 budget_remaining: budget,
74 policy_version: "v1".to_owned(),
75 sensitivity: "internal".to_owned(),
76 owner_agent: owner.to_owned(),
77 target_agents: Vec::new(),
78 parent_capsule: None,
79 created_at_epoch: SystemTime::now()
80 .duration_since(UNIX_EPOCH)
81 .map_or(0, |duration| duration.as_secs()),
82 }
83 }
84}
85impl DeltaTransfer {
86 pub fn is_efficient(&self, updated_ref_count: usize) -> bool {
92 self.added_refs.len() + self.removed_refs.len() < updated_ref_count
93 }
94}
95
96pub fn compute_delta(base: &ContextCapsuleV1, updated: &ContextCapsuleV1) -> DeltaTransfer {
98 let base_refs: HashSet<&str> = base.selected_refs.iter().map(String::as_str).collect();
99 let updated_refs: HashSet<&str> = updated.selected_refs.iter().map(String::as_str).collect();
100
101 DeltaTransfer {
102 base_ref: base.capsule_id.clone(),
103 added_refs: updated
104 .selected_refs
105 .iter()
106 .filter(|reference| !base_refs.contains(reference.as_str()))
107 .cloned()
108 .collect(),
109 removed_refs: base
110 .selected_refs
111 .iter()
112 .filter(|reference| !updated_refs.contains(reference.as_str()))
113 .cloned()
114 .collect(),
115 budget_delta: signed_difference(updated.budget_tokens, base.budget_tokens),
116 new_findings: Vec::new(),
117 }
118}
119
120pub fn apply_delta(base: &ContextCapsuleV1, delta: &DeltaTransfer) -> ContextCapsuleV1 {
122 let removed: HashSet<&str> = delta.removed_refs.iter().map(String::as_str).collect();
123 let mut selected_refs: Vec<String> = base
124 .selected_refs
125 .iter()
126 .filter(|reference| !removed.contains(reference.as_str()))
127 .cloned()
128 .collect();
129 let mut present: HashSet<String> = selected_refs.iter().cloned().collect();
130 for reference in &delta.added_refs {
131 if present.insert(reference.clone()) {
132 selected_refs.push(reference.clone());
133 }
134 }
135
136 let mut updated = base.clone();
137 updated.budget_tokens = apply_signed(base.budget_tokens, delta.budget_delta);
138 updated.budget_remaining = apply_signed(base.budget_remaining, delta.budget_delta);
139 updated.selected_refs = selected_refs;
140 updated.capsule_id = capsule_id(&updated.intent, &updated.selected_refs);
141 updated.manifest_ref.clone_from(&updated.capsule_id);
142 updated
143}
144
145pub fn dedup_siblings(capsules: &[ContextCapsuleV1]) -> DedupReport {
147 let mut counts: HashMap<&str, usize> = HashMap::new();
148 for capsule in capsules {
149 let refs: HashSet<&str> = capsule.selected_refs.iter().map(String::as_str).collect();
150 for reference in refs {
151 *counts.entry(reference).or_default() += 1;
152 }
153 }
154
155 let mut seen = HashSet::new();
156 let shared_refs = capsules
157 .iter()
158 .flat_map(|capsule| &capsule.selected_refs)
159 .filter(|reference| counts.get(reference.as_str()).copied().unwrap_or(0) > 1)
160 .filter(|reference| seen.insert(reference.as_str()))
161 .cloned()
162 .collect();
163 let unique_per_capsule = capsules
164 .iter()
165 .map(|capsule| {
166 capsule
167 .selected_refs
168 .iter()
169 .filter(|reference| counts.get(reference.as_str()).copied().unwrap_or(0) == 1)
170 .cloned()
171 .collect()
172 })
173 .collect();
174 let total_refs: usize = capsules
175 .iter()
176 .map(|capsule| capsule.selected_refs.len())
177 .sum();
178 let distinct_refs = counts.len();
179 let dedup_ratio = if total_refs == 0 {
180 0.0
181 } else {
182 (total_refs.saturating_sub(distinct_refs)) as f64 / total_refs as f64
183 };
184
185 DedupReport {
186 shared_refs,
187 unique_per_capsule,
188 dedup_ratio,
189 }
190}
191
192fn capsule_id(intent: &str, refs: &[String]) -> String {
193 let mut hasher = blake3::Hasher::new();
194 hasher.update(&(intent.len() as u64).to_le_bytes());
195 hasher.update(intent.as_bytes());
196 for reference in refs {
197 hasher.update(&(reference.len() as u64).to_le_bytes());
198 hasher.update(reference.as_bytes());
199 }
200 format!("blake3:{}", hasher.finalize().to_hex())
201}
202
203fn signed_difference(updated: u64, base: u64) -> i64 {
204 if updated >= base {
205 i64::try_from(updated - base).unwrap_or(i64::MAX)
206 } else {
207 -i64::try_from(base - updated).unwrap_or(i64::MAX)
208 }
209}
210
211fn apply_signed(value: u64, delta: i64) -> u64 {
212 if delta >= 0 {
213 value.saturating_add(delta.unsigned_abs())
214 } else {
215 value.saturating_sub(delta.unsigned_abs())
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::{ContextCapsuleV1, apply_delta, compute_delta, dedup_siblings};
222
223 fn capsule(refs: &[&str], budget: u64) -> ContextCapsuleV1 {
224 ContextCapsuleV1::new(
225 "handoff",
226 refs.iter()
227 .map(|reference| (*reference).to_owned())
228 .collect(),
229 budget,
230 "agent-a",
231 )
232 }
233
234 #[test]
235 fn new_capsule_has_content_addressed_id() {
236 let first = capsule(&["ref-a", "ref-b"], 100);
237 let second = capsule(&["ref-a", "ref-b"], 100);
238
239 assert_eq!(first.capsule_id, second.capsule_id);
240 assert!(first.capsule_id.starts_with("blake3:"));
241 assert_eq!(first.manifest_ref, first.capsule_id);
242 }
243
244 #[test]
245 fn compute_delta_finds_added_removed() {
246 let delta = compute_delta(&capsule(&["A", "B"], 100), &capsule(&["B", "C"], 80));
247
248 assert_eq!(delta.added_refs, ["C"]);
249 assert_eq!(delta.removed_refs, ["A"]);
250 assert_eq!(delta.budget_delta, -20);
251 }
252
253 #[test]
254 fn apply_delta_reconstructs_capsule() {
255 let base = capsule(&["A", "B"], 100);
256 let updated = capsule(&["B", "C"], 80);
257
258 assert_eq!(apply_delta(&base, &compute_delta(&base, &updated)), updated);
259 }
260
261 #[test]
262 fn delta_smaller_than_full() {
263 let base = capsule(&["ref-a", "ref-b", "ref-c"], 100);
264 let updated = capsule(&["ref-a", "ref-b", "ref-d"], 90);
265 let delta = compute_delta(&base, &updated);
266
267 assert!(
268 serde_json::to_vec(&delta).unwrap().len() < serde_json::to_vec(&updated).unwrap().len()
269 );
270 }
271
272 #[test]
273 fn dedup_siblings_finds_shared_refs() {
274 let capsules = [
275 capsule(&["ref1", "ref2", "only-a"], 100),
276 capsule(&["ref1", "ref2", "only-b"], 100),
277 capsule(&["ref1", "ref2", "only-c"], 100),
278 ];
279 let report = dedup_siblings(&capsules);
280
281 assert_eq!(report.shared_refs, ["ref1", "ref2"]);
282 assert_eq!(report.unique_per_capsule[0], ["only-a"]);
283 assert!((report.dedup_ratio - 4.0 / 9.0).abs() < f64::EPSILON);
284 }
285
286 #[test]
287 fn empty_capsule_delta_is_identity() {
288 let base = capsule(&[], 100);
289 let delta = compute_delta(&base, &base);
290
291 assert!(delta.added_refs.is_empty());
292 assert!(delta.removed_refs.is_empty());
293 assert_eq!(apply_delta(&base, &delta), base);
294 }
295}