1use super::compression::CompressionPipeline;
2use super::config::{ContextConfig, PromptBudgetConfig};
3use super::partitions::ContextPartitions;
4use super::policy::ContextPolicy;
5use super::pressure::{PressureAction, PressureMonitor};
6use super::renderer::InternalRenderedContext;
7use super::renewal::RenewalPolicy;
8use super::skill_catalog::SkillCatalog;
9use super::task_state::{TaskState, TaskUpdate};
10use super::token_engine::ContextTokenEngine;
11use crate::mm::handle::{Handle, HandleId, HandleKind, HandleTable, Residency};
12use crate::types::capability::{Capability, CapabilityKind, CapabilityManifest};
13use crate::types::message::{Content, ContentPart, CoreMessage, ToolSchema};
14use crate::types::skill::SkillMetadata;
15use compact_str::CompactString;
16
17pub const MEMORY_TOOL_NAME: &str = "memory";
18pub const KNOWLEDGE_TOOL_NAME: &str = "knowledge";
19pub const READ_RESULT_TOOL_NAME: &str = "read_result";
21
22const META_TOOL_NAMES: &[&str] = &[
25 "pace",
26 "update_plan",
27 "skill",
28 MEMORY_TOOL_NAME,
29 KNOWLEDGE_TOOL_NAME,
30 READ_RESULT_TOOL_NAME,
31 "submit_workflow_nodes",
32 "start_workflow",
33];
34
35pub(crate) fn is_meta_tool(name: &str) -> bool {
38 META_TOOL_NAMES.contains(&name)
39}
40
41pub(crate) const EXPOSURE_EXEMPT_META_TOOLS: &[&str] = &[
59 "skill",
60 MEMORY_TOOL_NAME,
61 KNOWLEDGE_TOOL_NAME,
62 "update_plan",
63 READ_RESULT_TOOL_NAME,
64];
65
66pub(crate) fn is_exposure_exempt_meta_tool(name: &str) -> bool {
69 EXPOSURE_EXEMPT_META_TOOLS.contains(&name)
70}
71
72#[doc(hidden)]
77pub struct ContextManager {
78 pub partitions: ContextPartitions,
79 pub max_tokens: u32,
80 pub config: ContextConfig,
81 pub engine: ContextTokenEngine,
82 pub prompt_budget: PromptBudgetConfig,
85 pub sprint: u32,
86 pub skills: SkillCatalog,
87 pub active_skills: std::collections::BTreeMap<CompactString, Option<u32>>,
95 pub stable_core_tools: std::collections::HashSet<CompactString>,
99 pub capabilities: CapabilityManifest,
100 pub memory_enabled: bool,
101 pub knowledge_enabled: bool,
102 pub plan_tool_enabled: bool,
103 last_observed_prompt_tokens: Option<u32>,
104 compression: CompressionPipeline,
105 pressure: PressureMonitor,
106 renewal: RenewalPolicy,
107
108 pub last_activity_ms: u64,
112
113 pub last_compact_ms: Option<u64>,
116
117 pub handles: HandleTable,
122 next_handle_id: HandleId,
124
125 frozen_history_len: usize,
132
133 pending_knowledge_sweeps: Vec<crate::context::partitions::KnowledgeSweep>,
136
137 knowledge_budget_warned: bool,
140 knowledge_reference_step: u64,
142}
143
144impl ContextManager {
145 pub fn new(max_tokens: u32) -> Self {
146 Self::with_config(
149 max_tokens,
150 ContextConfig::default(),
151 ContextTokenEngine::fallback_estimator(),
152 )
153 }
154
155 pub fn with_config(max_tokens: u32, config: ContextConfig, engine: ContextTokenEngine) -> Self {
156 let compression = CompressionPipeline::new(&config);
157 let pressure = PressureMonitor::new(max_tokens, config.clone());
158 let renewal = RenewalPolicy::from_config(&config);
159 let partitions = ContextPartitions::new(&config);
160 Self {
161 partitions,
162 max_tokens,
163 config,
164 engine,
165 prompt_budget: PromptBudgetConfig::default(),
166 sprint: 0,
167 skills: SkillCatalog::new(),
168 active_skills: std::collections::BTreeMap::new(),
169 stable_core_tools: std::collections::HashSet::new(),
170 capabilities: CapabilityManifest::new(),
171 memory_enabled: false,
172 knowledge_enabled: false,
173 plan_tool_enabled: false,
174 last_observed_prompt_tokens: None,
175 compression,
176 pressure,
177 renewal,
178 last_activity_ms: 0,
179 last_compact_ms: None,
180 handles: HandleTable::new(),
181 next_handle_id: 0,
182 frozen_history_len: 0,
183 pending_knowledge_sweeps: Vec::new(),
184 knowledge_budget_warned: false,
185 knowledge_reference_step: 0,
186 }
187 }
188
189 pub fn apply_context_policy(&mut self, policy: &ContextPolicy) {
191 policy.apply_to(&mut self.config);
192 self.compression = CompressionPipeline::new(&self.config);
193 self.pressure = PressureMonitor::new(self.max_tokens, self.config.clone());
194 self.renewal = RenewalPolicy::from_config(&self.config);
195 }
196
197 pub fn record_activity(&mut self, now_ms: u64) {
201 self.last_activity_ms = now_ms;
202 }
203
204 pub fn should_time_decay_compact(&self, now_ms: u64) -> bool {
207 let idle_ms = if let Some(last_compact) = self.last_compact_ms {
208 now_ms.saturating_sub(last_compact)
210 } else {
211 now_ms.saturating_sub(self.last_activity_ms)
213 };
214
215 let idle_minutes = idle_ms / 60_000;
216 idle_minutes >= self.config.micro_compact_idle_minutes as u64
217 }
218
219 pub fn recompute_handle_residency(&mut self) {
234 if self.rho() < self.config.collapse_threshold {
236 return;
237 }
238 let keep = self.config.preserved_tool_results;
239 let total = self
242 .handles
243 .all()
244 .iter()
245 .filter(|h| matches!(h.kind, HandleKind::ToolResult))
246 .count();
247 let cutoff = total.saturating_sub(keep);
248 for (i, handle) in self.handles.tool_result_handles_mut().enumerate() {
249 if i < cutoff && matches!(handle.residency, Residency::Resident) {
252 handle.residency = Residency::Collapsed;
253 }
254 }
255 }
256
257 pub fn reset_collapse_generation(&mut self) {
263 for handle in self.handles.all_mut() {
264 if matches!(handle.residency, Residency::Collapsed) {
265 handle.residency = Residency::Resident;
266 }
267 }
268 }
269
270 pub fn prune_orphaned_handles(&mut self) {
282 let live: std::collections::HashSet<CompactString> = self
283 .partitions
284 .history
285 .messages
286 .iter()
287 .flat_map(|m| match &m.content {
288 Content::Parts(parts) => parts
289 .iter()
290 .filter_map(|p| match p {
291 ContentPart::ToolResult { call_id, .. } => Some(call_id.clone()),
292 _ => None,
293 })
294 .collect::<Vec<_>>(),
295 _ => Vec::new(),
296 })
297 .collect();
298 self.handles.retain(|h| {
299 h.residency.payload_ref().is_some()
300 || h.source.as_ref().is_none_or(|s| live.contains(s))
301 });
302 }
303
304 pub fn set_payload_residency(
319 &mut self,
320 source: &str,
321 kind: HandleKind,
322 tokens: u32,
323 residency: Residency,
324 ) -> Option<Residency> {
325 if let Some(handle) = self
326 .handles
327 .all_mut()
328 .iter_mut()
329 .find(|h| h.source.as_deref() == Some(source))
330 {
331 let previous = std::mem::replace(&mut handle.residency, residency);
332 handle.tokens = tokens;
333 return Some(previous);
334 }
335 let id = self.alloc_handle_id();
336 self.handles.insert(Handle {
337 id,
338 kind,
339 residency,
340 tokens,
341 source: Some(source.into()),
342 });
343 None
344 }
345
346 pub fn payload_residency(&self, source: &str) -> Option<&Residency> {
353 self.handles.residency_for_source(source)
354 }
355
356 pub fn rho(&self) -> f64 {
363 self.pressure.pressure(
364 &self.partitions,
365 &self.engine,
366 self.last_observed_prompt_tokens,
367 )
368 }
369
370 pub fn set_observed_prompt_tokens(&mut self, tokens: u32) {
371 self.last_observed_prompt_tokens = Some(tokens);
372 }
373
374 pub fn should_compress(&self) -> PressureAction {
375 self.pressure.recommend(self.rho())
381 }
382
383 pub fn compress(
384 &mut self,
385 action: PressureAction,
386 ) -> (u32, Option<String>, Vec<CoreMessage>, Option<usize>) {
387 self.compress_with_time(action, None)
388 }
389
390 pub fn compress_with_time(
391 &mut self,
392 action: PressureAction,
393 now_ms: Option<u64>,
394 ) -> (u32, Option<String>, Vec<CoreMessage>, Option<usize>) {
395 let target = self.config.target_tokens(self.max_tokens);
396 self.compress_with_target(action, target, now_ms)
397 }
398
399 pub fn force_compress(&mut self) -> (u32, Option<String>, Vec<CoreMessage>, Option<usize>) {
400 self.compress_with_target(PressureAction::AutoCompact, 0, None)
401 }
402
403 pub fn compress_with_target(
410 &mut self,
411 action: PressureAction,
412 target_tokens: u32,
413 now_ms: Option<u64>,
414 ) -> (u32, Option<String>, Vec<CoreMessage>, Option<usize>) {
415 let result = self.compression.compress(
416 &mut self.partitions,
417 action,
418 self.max_tokens,
419 target_tokens,
420 &self.engine,
421 );
422 if let Some(ts) = now_ms {
423 self.last_compact_ms = Some(ts);
424 }
425 if !result.2.is_empty() {
427 self.prune_orphaned_handles();
428 self.reset_collapse_generation();
431 self.sweep_knowledge_at_boundary();
434 }
435 if result.3.is_some() {
442 self.frozen_history_len = self.partitions.history.messages.len();
443 }
444 result
445 }
446
447 pub fn plan_compaction_params(&self) -> (u32, usize) {
451 (
452 self.config.target_tokens(self.max_tokens),
453 self.config.preserve_recent_turns,
454 )
455 }
456
457 pub fn should_renew(&self) -> bool {
460 self.renewal
461 .should_renew(&self.pressure, &self.partitions, &self.engine)
462 }
463
464 pub fn renew(&mut self) {
465 self.partitions = self
466 .renewal
467 .renew(&self.partitions, self.max_tokens, &self.engine);
468 self.sprint += 1;
469 self.prune_orphaned_handles();
472 self.reset_collapse_generation();
473 self.sweep_knowledge_at_boundary();
475 self.frozen_history_len = self.partitions.history.messages.len();
477 }
478
479 pub fn set_prompt_budget(&mut self, prompt_budget: PromptBudgetConfig) {
482 self.prompt_budget = prompt_budget;
483 }
484
485 pub fn available_input_tokens(&self) -> u32 {
486 self.max_tokens
487 .saturating_sub(self.prompt_budget.reserved_tokens())
488 }
489
490 pub fn render(&self) -> InternalRenderedContext {
491 super::renderer::render_projected(
492 &self.partitions,
493 self.available_input_tokens(),
494 &self.engine,
495 self.config.preserve_recent_units,
496 &self.handles,
497 self.frozen_history_len,
498 self.config.collapse_assistant_narration,
499 )
500 }
501
502 pub fn push_history(&mut self, msg: CoreMessage, tokens: u32) {
505 self.knowledge_reference_step = self.knowledge_reference_step.saturating_add(1);
506 self.partitions
507 .knowledge
508 .observe_references(&msg, self.knowledge_reference_step);
509 if let Content::Parts(parts) = &msg.content {
513 for part in parts {
514 if let ContentPart::ToolResult {
515 call_id, output, ..
516 } = part
517 {
518 let id = self.alloc_handle_id();
519 let tok = self.engine.count(output).max(1);
520 self.handles.insert(Handle::resident_for(
521 id,
522 HandleKind::ToolResult,
523 tok,
524 call_id.clone(),
525 ));
526 }
527 }
528 }
529 self.partitions.history.push(msg, tokens);
530 }
531
532 fn alloc_handle_id(&mut self) -> HandleId {
533 let id = self.next_handle_id;
534 self.next_handle_id = self.next_handle_id.wrapping_add(1);
535 id
536 }
537
538 pub fn next_handle_id(&self) -> HandleId {
543 self.next_handle_id
544 }
545
546 pub fn restore_next_handle_id(&mut self, next: HandleId) {
552 self.next_handle_id = next;
553 }
554
555 pub fn frozen_history_len(&self) -> usize {
556 self.frozen_history_len
557 }
558
559 pub fn restore_frozen_history_len(&mut self, len: usize) -> bool {
560 if len > self.partitions.history.messages.len() {
561 return false;
562 }
563 self.frozen_history_len = len;
564 true
565 }
566
567 pub fn push_knowledge(&mut self, msg: CoreMessage, tokens: u32) {
569 self.partitions.knowledge.push(msg, tokens);
570 }
571
572 pub fn push_knowledge_entry(
576 &mut self,
577 key: Option<CompactString>,
578 msg: CoreMessage,
579 tokens: u32,
580 pinned: bool,
581 ) {
582 self.partitions
583 .knowledge
584 .push_entry(key, msg, tokens, pinned);
585 }
586
587 pub fn remove_knowledge(&mut self, key: &str) -> bool {
590 self.partitions.knowledge.remove(key)
591 }
592
593 fn sweep_knowledge_at_boundary(&mut self) {
597 let sweep = self.partitions.knowledge.sweep_at_boundary();
598 if sweep.changed {
599 if !sweep.removed_keys.is_empty() {
603 self.partitions.signals.push(format!(
604 "[KNOWLEDGE] entries removed at this boundary: {} — re-fetch via the memory tool if still needed.",
605 sweep.removed_keys.join(", ")
606 ));
607 }
608 self.pending_knowledge_sweeps.push(sweep);
609 }
610 self.knowledge_budget_warned = false;
612 }
613
614 pub fn enforce_knowledge_budget(&mut self) -> Option<(u32, u32)> {
623 let ratio = self.config.knowledge_budget_ratio;
624 if ratio <= 0.0 {
625 return None;
626 }
627 let budget = (self.max_tokens as f64 * ratio) as u32;
628 let used = self.partitions.knowledge.token_count;
629 if used <= budget {
630 return None;
631 }
632 let marked: u32 = self
633 .partitions
634 .knowledge
635 .entries
636 .iter()
637 .filter(|e| e.evict_at_boundary)
638 .map(|e| e.tokens)
639 .sum();
640 let mut projected = used.saturating_sub(marked);
641 let mut candidates = self
642 .partitions
643 .knowledge
644 .entries
645 .iter()
646 .enumerate()
647 .filter(|(_, entry)| {
648 !entry.evict_at_boundary
649 && !entry.pinned
650 && !entry
651 .key
652 .as_deref()
653 .is_some_and(|key| key.starts_with("skill:"))
654 })
655 .map(|(index, _)| {
656 let score = self
657 .partitions
658 .knowledge
659 .retention_score(index, self.knowledge_reference_step)
660 .unwrap_or(i64::MIN);
661 (score, index)
662 })
663 .collect::<Vec<_>>();
664 candidates.sort_by(|left, right| left.0.cmp(&right.0).then(left.1.cmp(&right.1)));
665 for (_, index) in candidates {
666 if projected <= budget {
667 break;
668 }
669 let entry = &mut self.partitions.knowledge.entries[index];
670 entry.evict_at_boundary = true;
671 projected = projected.saturating_sub(entry.tokens);
672 }
673 if self.knowledge_budget_warned {
674 return None;
675 }
676 self.knowledge_budget_warned = true;
677 Some((used, budget))
678 }
679
680 pub fn take_knowledge_sweeps(&mut self) -> Vec<crate::context::partitions::KnowledgeSweep> {
682 std::mem::take(&mut self.pending_knowledge_sweeps)
683 }
684
685 pub fn push_signal(&mut self, text: String) {
689 self.partitions.signals.push(text);
690 }
691
692 pub fn record_directive(&mut self, text: impl Into<String>) {
696 self.partitions.task_state.record_directive(text);
697 }
698
699 pub fn init_task(&mut self, goal: String, criteria: Vec<String>) {
702 self.partitions.task_state = TaskState {
703 goal,
704 criteria,
705 ..Default::default()
706 };
707 }
708
709 pub fn update_task(&mut self, update: TaskUpdate) {
710 self.partitions.task_state.apply(update);
711 }
712
713 pub fn note_tool_actions(&mut self, calls: &[(String, String)]) {
720 let summary = calls
721 .iter()
722 .filter(|(name, _)| !is_meta_tool(name))
723 .map(|(name, args)| {
724 if args.is_empty() {
725 name.clone()
726 } else {
727 format!("{name}({args})")
728 }
729 })
730 .collect::<Vec<_>>()
731 .join(", ");
732 self.partitions.task_state.note_actions(summary);
733 }
734
735 pub fn set_available_skills(&mut self, skills: Vec<SkillMetadata>) {
740 self.capabilities.remove_kind(CapabilityKind::Skill);
741 for skill in &skills {
742 self.capabilities.add_skill(skill.clone());
743 }
744 self.skills.set_available(skills);
745 }
746
747 pub fn set_stable_core_tools(&mut self, ids: impl IntoIterator<Item = CompactString>) {
749 self.stable_core_tools = ids.into_iter().collect();
750 }
751
752 pub fn activate_skill(&mut self, name: impl Into<CompactString>) -> bool {
757 self.activate_skill_leased(name, None)
758 }
759
760 pub fn activate_skill_leased(
763 &mut self,
764 name: impl Into<CompactString>,
765 expires_at_turn: Option<u32>,
766 ) -> bool {
767 self.active_skills
768 .insert(name.into(), expires_at_turn)
769 .is_none()
770 }
771
772 pub fn deactivate_skill(&mut self, name: &str) -> bool {
776 if self.active_skills.remove(name).is_none() {
777 return false;
778 }
779 self.partitions.knowledge.remove(&format!("skill:{name}"));
780 true
781 }
782
783 pub fn sweep_expired_skill_leases(&mut self, current_turn: u32) {
786 let expired: Vec<CompactString> = self
787 .active_skills
788 .iter()
789 .filter(|(_, lease)| lease.is_some_and(|t| current_turn >= t))
790 .map(|(name, _)| name.clone())
791 .collect();
792 for name in expired {
793 self.deactivate_skill(&name);
794 self.partitions.signals.push(format!(
796 "[SKILL] lease expired: {name} unloaded; the full toolset is restored."
797 ));
798 }
799 }
800
801 pub fn active_skill_tool_filter(&self) -> Option<std::collections::HashSet<CompactString>> {
807 if self.active_skills.is_empty() {
808 return None;
809 }
810 let mut union = std::collections::HashSet::new();
811 for name in self.active_skills.keys() {
812 let declared = self.skills.allowed_tools(name);
813 if declared.is_empty() {
814 return None; }
816 union.extend(declared.iter().cloned());
817 }
818 Some(union)
819 }
820
821 pub fn active_skill_capabilities(&self) -> Vec<Capability> {
825 self.active_skills
826 .keys()
827 .flat_map(|name| self.skills.capability_grants(name).iter().cloned())
828 .collect()
829 }
830
831 pub fn skill_capability_grants(&self, name: &str) -> &[Capability] {
832 self.skills.capability_grants(name)
833 }
834
835 pub fn skill_tool_schema(&self) -> Option<ToolSchema> {
836 self.skills.build_tool_schema()
837 }
838
839 pub fn skill_available(&self, name: &str) -> bool {
842 self.skills.is_available(name)
843 }
844
845 pub fn set_memory_enabled(&mut self, enabled: bool) {
848 self.memory_enabled = enabled;
849 if enabled {
850 self.capabilities.add_marker(
851 CapabilityKind::Memory,
852 MEMORY_TOOL_NAME,
853 "Search long-term memory through the memory meta-tool.",
854 );
855 } else {
856 self.capabilities
857 .remove(CapabilityKind::Memory, MEMORY_TOOL_NAME);
858 }
859 }
860
861 pub fn set_knowledge_enabled(&mut self, enabled: bool) {
862 self.knowledge_enabled = enabled;
863 if enabled {
864 self.capabilities.add_marker(
865 CapabilityKind::Knowledge,
866 KNOWLEDGE_TOOL_NAME,
867 "Search external knowledge through the knowledge meta-tool.",
868 );
869 } else {
870 self.capabilities
871 .remove(CapabilityKind::Knowledge, KNOWLEDGE_TOOL_NAME);
872 }
873 }
874
875 pub fn set_plan_tool_enabled(&mut self, enabled: bool) {
876 self.plan_tool_enabled = enabled;
877 if enabled {
878 self.capabilities.add_marker(
879 CapabilityKind::Tool,
880 "update_plan",
881 "Update task plan and progress through the planning meta-tool.",
882 );
883 } else {
884 self.capabilities
885 .remove(CapabilityKind::Tool, "update_plan");
886 }
887 }
888
889 pub fn capability_inventory(&self) -> String {
890 self.capabilities.format_inventory()
891 }
892
893 pub fn meta_tool_schemas(&self) -> Vec<ToolSchema> {
894 let mut tools = Vec::new();
895 if let Some(t) = self.skill_tool_schema() {
896 tools.push(t);
897 }
898 if let Some(t) = self.memory_tool_schema() {
899 tools.push(t);
900 }
901 if let Some(t) = self.knowledge_tool_schema() {
902 tools.push(t);
903 }
904 if let Some(t) = self.plan_tool_schema() {
905 tools.push(t);
906 }
907 if let Some(t) = self.read_result_tool_schema() {
908 tools.push(t);
909 }
910 tools.sort_by(|a, b| a.name.cmp(&b.name));
911 tools
912 }
913
914 pub fn read_result_tool_schema(&self) -> Option<ToolSchema> {
920 let any_evicted = self
921 .handles
922 .all()
923 .iter()
924 .any(|h| !h.residency.occupies_context());
925 if !any_evicted {
926 return None;
927 }
928 Some(ToolSchema {
929 name: CompactString::new(READ_RESULT_TOOL_NAME),
930 description: "Re-read the full content of a tool result that was truncated to save \
931 context (marked '[Output truncated: … call the read_result tool …]'). \
932 Pass that marker's call_id; use offset/max_bytes to page through \
933 large content."
934 .to_string(),
935 parameters: serde_json::json!({
936 "type": "object",
937 "properties": {
938 "call_id": { "type": "string" },
939 "offset": { "type": "integer", "description": "Byte offset to start from (default 0)." },
940 "max_bytes": { "type": "integer", "description": "Max bytes to return (default 4000)." }
941 },
942 "required": ["call_id"]
943 }),
944 })
945 }
946
947 pub fn plan_tool_schema(&self) -> Option<ToolSchema> {
948 if !self.plan_tool_enabled {
949 return None;
950 }
951 Some(ToolSchema {
952 name: CompactString::new("update_plan"),
953 description: "Update your task plan and progress. Call this after completing a step or when the plan changes.".to_string(),
954 parameters: serde_json::json!({
955 "type": "object",
956 "properties": {
957 "plan": { "type": "array", "items": { "type": "string" } },
958 "current_step": { "type": "integer" },
959 "progress": { "type": "string" },
960 "blocked_on": { "type": "array", "items": { "type": "string" } }
961 }
962 }),
963 })
964 }
965
966 pub fn memory_tool_schema(&self) -> Option<ToolSchema> {
967 if !self.memory_enabled {
968 return None;
969 }
970 Some(ToolSchema {
971 name: CompactString::new(MEMORY_TOOL_NAME),
972 description:
973 "Search your long-term memory for relevant past experiences and knowledge."
974 .to_string(),
975 parameters: serde_json::json!({
976 "type": "object",
977 "properties": {
978 "query": { "type": "string" },
979 "top_k": { "type": "integer" }
980 },
981 "required": ["query"]
982 }),
983 })
984 }
985
986 pub fn knowledge_tool_schema(&self) -> Option<ToolSchema> {
987 if !self.knowledge_enabled {
988 return None;
989 }
990 Some(ToolSchema {
991 name: CompactString::new(KNOWLEDGE_TOOL_NAME),
992 description:
993 "Search the external knowledge base for facts, documentation, or reference data."
994 .to_string(),
995 parameters: serde_json::json!({
996 "type": "object",
997 "properties": {
998 "query": { "type": "string" },
999 "top_k": { "type": "integer" }
1000 },
1001 "required": ["query"]
1002 }),
1003 })
1004 }
1005}
1006
1007#[cfg(test)]
1008mod tests {
1009 use super::*;
1010 use crate::context::task_state::PlanStep;
1011 use crate::types::message::CoreMessage;
1012 use crate::types::skill::SkillMetadata;
1013
1014 #[test]
1015 fn note_tool_actions_keys_on_name_and_args_so_legit_loops_dont_false_stop() {
1016 let mut mgr = ContextManager::new(100_000);
1019 mgr.init_task("process items".to_string(), vec![]);
1020 mgr.note_tool_actions(&[("step".to_string(), "{\"n\":1}".to_string())]);
1021 mgr.note_tool_actions(&[("step".to_string(), "{\"n\":2}".to_string())]);
1022 mgr.note_tool_actions(&[("step".to_string(), "{\"n\":3}".to_string())]);
1023 assert_eq!(
1024 mgr.partitions.task_state.recent_actions,
1025 ["step({\"n\":1})", "step({\"n\":2})", "step({\"n\":3})"]
1026 );
1027 let txt = mgr
1028 .render()
1029 .state_turn
1030 .unwrap()
1031 .content
1032 .as_text()
1033 .unwrap()
1034 .to_string();
1035 assert!(
1036 !txt.contains("STOP:"),
1037 "same-tool/diff-args loop must not trip STOP: {txt}"
1038 );
1039
1040 let mut mgr2 = ContextManager::new(100_000);
1042 mgr2.init_task("g".to_string(), vec![]);
1043 for _ in 0..3 {
1044 mgr2.note_tool_actions(&[("document_read".to_string(), "{\"id\":\"x\"}".to_string())]);
1045 }
1046 let txt2 = mgr2
1047 .render()
1048 .state_turn
1049 .unwrap()
1050 .content
1051 .as_text()
1052 .unwrap()
1053 .to_string();
1054 assert!(
1055 txt2.contains("STOP:"),
1056 "identical repeated call must trip STOP: {txt2}"
1057 );
1058
1059 let mut mgr3 = ContextManager::new(100_000);
1061 mgr3.init_task("g".to_string(), vec![]);
1062 mgr3.note_tool_actions(&[(
1063 "update_plan".to_string(),
1064 "{\"current_step\":1}".to_string(),
1065 )]);
1066 assert!(mgr3.partitions.task_state.recent_actions.is_empty());
1067 }
1068
1069 #[test]
1070 fn manager_renew_advances_sprint_and_keeps_goal() {
1071 let mut mgr = ContextManager::new(1_000);
1072 mgr.init_task("test goal".to_string(), vec![]);
1073 mgr.partitions.system.push(CoreMessage::system("rules"), 10);
1074 for i in 0..10 {
1075 mgr.push_history(CoreMessage::user(format!("msg {i}")), 50);
1076 }
1077 mgr.renew();
1078 assert_eq!(mgr.partitions.task_state.goal, "test goal");
1079 assert_eq!(mgr.sprint, 1);
1080 }
1081
1082 #[test]
1083 fn compress_only_touches_history() {
1084 let mut mgr = ContextManager::new(1_000);
1085 mgr.push_knowledge(CoreMessage::system("knowledge content"), 100);
1086 for _ in 0..30 {
1087 mgr.push_history(CoreMessage::user("history msg"), 50);
1088 }
1089 let knowledge_before = mgr.partitions.knowledge.token_count;
1090 let history_before = mgr.partitions.history.token_count;
1091 mgr.compress(PressureAction::AutoCompact);
1092 assert_eq!(mgr.partitions.knowledge.token_count, knowledge_before);
1093 assert!(mgr.partitions.history.token_count < history_before);
1094 }
1095
1096 #[test]
1097 fn init_task_sets_goal_and_criteria() {
1098 let mut mgr = ContextManager::new(1_000);
1099 mgr.init_task("analyse data".to_string(), vec!["criterion A".to_string()]);
1100 assert_eq!(mgr.partitions.task_state.goal, "analyse data");
1101 assert_eq!(mgr.partitions.task_state.criteria, ["criterion A"]);
1102 }
1103
1104 #[test]
1105 fn update_task_applies_plan() {
1106 let mut mgr = ContextManager::new(1_000);
1107 mgr.init_task("g".to_string(), vec![]);
1108 mgr.update_task(TaskUpdate {
1109 plan: Some(vec!["step 1".to_string(), "step 2".to_string()]),
1110 current_step: Some(0),
1111 ..Default::default()
1112 });
1113 assert_eq!(mgr.partitions.task_state.plan.len(), 2);
1114 assert_eq!(mgr.partitions.task_state.current_step, Some(0));
1115 }
1116
1117 #[test]
1118 fn task_state_survives_autocompact() {
1119 let mut mgr = ContextManager::new(1_000);
1120 mgr.init_task("survive compression".to_string(), vec![]);
1121 mgr.update_task(TaskUpdate {
1122 plan: Some(vec!["fetch data".to_string(), "analyse".to_string()]),
1123 ..Default::default()
1124 });
1125 for _ in 0..10 {
1126 mgr.push_history(CoreMessage::user("filler"), 50);
1127 }
1128 mgr.compress(PressureAction::AutoCompact);
1129 assert_eq!(mgr.partitions.task_state.goal, "survive compression");
1130 assert_eq!(mgr.partitions.task_state.plan.len(), 2);
1131 }
1132
1133 #[test]
1134 fn render_includes_task_state_in_state_turn_not_system() {
1135 let mut mgr = ContextManager::new(10_000);
1136 mgr.init_task("find anomalies".to_string(), vec![]);
1137 let rc = mgr.render();
1138 assert!(
1139 !rc.system_text.contains("[TASK STATE]"),
1140 "task_state must not be in system_text"
1141 );
1142 let state = rc.state_turn.as_ref().expect("should have a state turn");
1144 assert!(
1145 state
1146 .content
1147 .as_text()
1148 .unwrap()
1149 .contains("[TASK STATE] goal: find anomalies")
1150 );
1151 }
1152
1153 #[test]
1154 fn renewal_keeps_open_plan_steps_in_task_state() {
1155 let mut mgr = ContextManager::new(1_000);
1156 mgr.init_task("g".to_string(), vec![]);
1157 mgr.partitions.task_state.plan = vec![
1158 PlanStep {
1159 label: "done".to_string(),
1160 done: true,
1161 },
1162 PlanStep {
1163 label: "pending".to_string(),
1164 done: false,
1165 },
1166 ];
1167 mgr.renew();
1168 assert_eq!(mgr.partitions.task_state.open_steps(), vec!["pending"]);
1169 }
1170
1171 #[test]
1174 fn auto_compact_entry_logs_auto_compact_action() {
1175 let mut mgr = ContextManager::new(1_000);
1183 for i in 0..40 {
1184 mgr.push_history(
1185 CoreMessage::user(format!("turn {i}: {}", "ctx ".repeat(40))),
1186 200,
1187 );
1188 }
1189 let (saved, summary, _, _) = mgr.force_compress();
1190 assert!(saved > 0, "force_compress should compact a large history");
1191 assert!(
1192 summary.is_some(),
1193 "auto-compact summarizes the archived turns"
1194 );
1195 let actions: Vec<&str> = mgr
1196 .partitions
1197 .task_state
1198 .compression_log
1199 .iter()
1200 .map(|e| e.action.as_str())
1201 .collect();
1202 assert!(
1203 actions.last() == Some(&"auto_compact"),
1204 "auto-compact entry must log an auto_compact action; got {actions:?}"
1205 );
1206 }
1207
1208 #[test]
1209 fn skill_tool_schema_empty_when_no_skills() {
1210 let mgr = ContextManager::new(10_000);
1211 assert!(mgr.skill_tool_schema().is_none());
1212 }
1213
1214 #[test]
1215 fn skill_tool_schema_present_when_registered() {
1216 let mut mgr = ContextManager::new(10_000);
1217 mgr.set_available_skills(vec![SkillMetadata::new("debug", "Debug helper")]);
1218 assert!(
1219 mgr.skill_tool_schema()
1220 .unwrap()
1221 .description
1222 .contains("debug")
1223 );
1224 }
1225
1226 #[test]
1227 fn available_skills_are_reflected_in_capability_manifest() {
1228 let mut mgr = ContextManager::new(1_000);
1229 mgr.set_available_skills(vec![SkillMetadata::new("debug", "Debug helper")]);
1230 let inventory = mgr.capability_inventory();
1231 assert!(inventory.contains("debug"));
1232 assert!(inventory.contains("Debug helper"));
1233 }
1234
1235 #[test]
1236 fn toggled_meta_tools_are_reflected_in_capability_manifest() {
1237 let mut mgr = ContextManager::new(1_000);
1238 mgr.set_memory_enabled(true);
1239 assert!(mgr.capability_inventory().contains(MEMORY_TOOL_NAME));
1240 mgr.set_memory_enabled(false);
1241 assert!(!mgr.capability_inventory().contains(MEMORY_TOOL_NAME));
1242 }
1243
1244 #[test]
1245 fn meta_tool_schemas_are_sorted() {
1246 let mut mgr = ContextManager::new(1_000);
1247 mgr.set_available_skills(vec![SkillMetadata::new("debug", "Debug helper")]);
1248 mgr.set_memory_enabled(true);
1249 mgr.set_knowledge_enabled(true);
1250 let names = mgr
1251 .meta_tool_schemas()
1252 .into_iter()
1253 .map(|s| s.name.to_string())
1254 .collect::<Vec<_>>();
1255 assert_eq!(names, ["knowledge", "memory", "skill"]);
1256 }
1257
1258 #[test]
1259 fn b1_active_skill_state_and_tool_filter() {
1260 let mut mgr = ContextManager::new(1_000);
1261 let mut debug = SkillMetadata::new("debug", "Debug helper");
1262 debug.allowed_tools = vec![CompactString::new("read"), CompactString::new("grep")];
1263 let mut review = SkillMetadata::new("review", "Reviewer");
1264 review.allowed_tools = vec![CompactString::new("git_diff")];
1265 let plain = SkillMetadata::new("plain", "No tools declared"); mgr.set_available_skills(vec![debug, review, plain]);
1267
1268 assert!(mgr.active_skill_tool_filter().is_none());
1270
1271 assert!(mgr.activate_skill("debug"));
1273 assert!(!mgr.activate_skill("debug")); let f = mgr.active_skill_tool_filter().unwrap();
1277 assert_eq!(f.len(), 2);
1278 assert!(f.contains(&CompactString::new("read")) && f.contains(&CompactString::new("grep")));
1279
1280 mgr.activate_skill("review");
1282 let f = mgr.active_skill_tool_filter().unwrap();
1283 assert_eq!(f.len(), 3);
1284 assert!(f.contains(&CompactString::new("git_diff")));
1285
1286 mgr.activate_skill("plain");
1288 assert!(mgr.active_skill_tool_filter().is_none());
1289 }
1290
1291 #[test]
1292 fn active_skill_capability_grants_follow_activation_deactivation_and_lease_expiry() {
1293 use crate::types::capability::{
1294 ActionSet, Capability, CapabilityId, ConstraintSet, Principal, ResourceSelector,
1295 };
1296
1297 let grant = Capability {
1298 id: CapabilityId("read-src".into()),
1299 kind: CapabilityKind::Tool,
1300 resource: ResourceSelector("/repo/src/**".into()),
1301 actions: ActionSet(["read".into()].into_iter().collect()),
1302 constraints: ConstraintSet::default(),
1303 lease: None,
1304 delegatable: false,
1305 issuer: Principal("root".into()),
1306 };
1307 let mut review = SkillMetadata::new("review", "Review source files");
1308 review.capability_grants = vec![grant.clone()];
1309
1310 let mut mgr = ContextManager::new(1_000);
1311 mgr.set_available_skills(vec![review]);
1312 assert!(mgr.active_skill_capabilities().is_empty());
1313
1314 mgr.activate_skill("review");
1315 assert_eq!(mgr.active_skill_capabilities(), vec![grant.clone()]);
1316
1317 mgr.deactivate_skill("review");
1318 assert!(mgr.active_skill_capabilities().is_empty());
1319
1320 mgr.activate_skill_leased("review", Some(3));
1321 mgr.sweep_expired_skill_leases(2);
1322 assert_eq!(mgr.active_skill_capabilities(), vec![grant]);
1323
1324 mgr.sweep_expired_skill_leases(3);
1325 assert!(mgr.active_skill_capabilities().is_empty());
1326 }
1327
1328 #[test]
1329 fn update_collapse_mode_collapses_old_tool_results_under_pressure() {
1330 let mut mgr = ContextManager::new(1_000);
1331 for i in 0..10 {
1332 let m = CoreMessage::tool(vec![ContentPart::ToolResult {
1333 call_id: format!("c{i}").into(),
1334 output: "x".repeat(40),
1335 is_error: false,
1336 durable_content: None,
1337 }]);
1338 mgr.push_history(m, 40);
1339 }
1340 mgr.set_observed_prompt_tokens(950); assert!(mgr.rho() >= mgr.config.collapse_threshold);
1343
1344 mgr.recompute_handle_residency();
1345 assert_eq!(
1347 mgr.handles.residency_for_source("c0"),
1348 Some(&Residency::Collapsed)
1349 );
1350 assert_eq!(
1351 mgr.handles.residency_for_source("c9"),
1352 Some(&Residency::Resident)
1353 );
1354
1355 mgr.set_observed_prompt_tokens(100); mgr.recompute_handle_residency();
1359 assert_eq!(
1360 mgr.handles.residency_for_source("c0"),
1361 Some(&Residency::Collapsed),
1362 "collapse is sticky until a compaction boundary"
1363 );
1364
1365 mgr.reset_collapse_generation();
1367 assert_eq!(
1368 mgr.handles.residency_for_source("c0"),
1369 Some(&Residency::Resident)
1370 );
1371 }
1372
1373 #[test]
1374 fn frozen_prefix_len_anchors_at_compaction_and_holds_across_appends() {
1375 let mut mgr = ContextManager::new(1_000);
1376 for i in 0..30 {
1378 mgr.push_history(
1379 CoreMessage::user(format!("turn {i}: {}", "ctx ".repeat(30))),
1380 150,
1381 );
1382 }
1383 assert!(
1384 mgr.render().frozen_prefix_len.is_none(),
1385 "no frozen region before any compaction"
1386 );
1387
1388 let (saved, _, archived, _) = mgr.compress(PressureAction::AutoCompact);
1389 assert!(saved > 0 && !archived.is_empty(), "expected archival");
1390
1391 assert!(
1393 mgr.render().frozen_prefix_len.is_none(),
1394 "deep == tail right after compaction"
1395 );
1396
1397 mgr.push_history(CoreMessage::user("new 1"), 5);
1399 let f1 = mgr
1400 .render()
1401 .frozen_prefix_len
1402 .expect("frozen region exists once the tail grows");
1403 mgr.push_history(CoreMessage::assistant("reply 1"), 5);
1404 mgr.push_history(CoreMessage::user("new 2"), 5);
1405 let rc = mgr.render();
1406 let f2 = rc.frozen_prefix_len.expect("frozen region holds");
1407 assert_eq!(
1408 f1, f2,
1409 "the deep boundary is fixed between compactions; only the tail grows"
1410 );
1411 assert!(
1412 f2 < rc.turns.len(),
1413 "deep boundary is distinct from the rolling tail"
1414 );
1415 }
1416
1417 #[test]
1418 fn frozen_boundary_holds_through_a_prefix_safe_compaction() {
1419 let mut mgr = ContextManager::new(10_000);
1422 for i in 0..5 {
1423 mgr.push_history(CoreMessage::user(format!("m{i}")), 5);
1424 }
1425 mgr.frozen_history_len = 3; let (_, _, _, cache_at) = mgr.compress(PressureAction::None);
1430 assert!(cache_at.is_none(), "no-op compaction is prefix-safe");
1431 assert_eq!(
1432 mgr.frozen_history_len, 3,
1433 "prefix-safe compaction preserves the deep-cache anchor"
1434 );
1435 }
1436
1437 #[test]
1438 fn collapse_generation_resets_on_autocompact() {
1439 let mut mgr = ContextManager::new(1_000);
1440 for i in 0..20 {
1443 mgr.push_history(tool_result_msg(&format!("c{i}"), &"x".repeat(120)), 60);
1444 }
1445 mgr.set_observed_prompt_tokens(980); mgr.recompute_handle_residency();
1447 assert_eq!(
1448 mgr.handles.residency_for_source("c0"),
1449 Some(&Residency::Collapsed)
1450 );
1451
1452 let (saved, _, archived, _) = mgr.compress(PressureAction::AutoCompact);
1453 assert!(saved > 0 && !archived.is_empty(), "expected archival");
1454
1455 for h in mgr.handles.all() {
1458 if matches!(h.kind, HandleKind::ToolResult) {
1459 assert_eq!(
1460 h.residency,
1461 Residency::Resident,
1462 "generation reset un-collapses survivors"
1463 );
1464 }
1465 }
1466 }
1467
1468 #[test]
1469 fn push_history_indexes_tool_results_as_resident_handles() {
1470 let mut mgr = ContextManager::new(10_000);
1471 let msg = CoreMessage::tool(vec![ContentPart::ToolResult {
1472 call_id: "call_1".into(),
1473 output: "the tool output".to_string(),
1474 is_error: false,
1475 durable_content: None,
1476 }]);
1477 mgr.push_history(msg, 20);
1478 assert_eq!(mgr.handles.all().len(), 1);
1480 assert_eq!(
1481 mgr.handles.residency_for_source("call_1"),
1482 Some(&Residency::Resident)
1483 );
1484 mgr.push_history(CoreMessage::user("hello"), 5);
1486 assert_eq!(mgr.handles.all().len(), 1);
1487 }
1488
1489 fn tool_result_msg(call_id: &str, output: &str) -> CoreMessage {
1492 CoreMessage::tool(vec![ContentPart::ToolResult {
1493 call_id: call_id.into(),
1494 output: output.to_string(),
1495 is_error: false,
1496 durable_content: None,
1497 }])
1498 }
1499
1500 #[test]
1501 fn prune_orphaned_handles_drops_handles_whose_message_left_history() {
1502 let mut mgr = ContextManager::new(10_000);
1503 mgr.push_history(tool_result_msg("c0", "out 0"), 20);
1504 mgr.push_history(tool_result_msg("c1", "out 1"), 20);
1505 assert_eq!(mgr.handles.all().len(), 2);
1506
1507 mgr.partitions.history.messages.remove(0);
1509 mgr.prune_orphaned_handles();
1510
1511 assert_eq!(mgr.handles.all().len(), 1);
1513 assert!(mgr.handles.residency_for_source("c0").is_none());
1514 assert_eq!(
1515 mgr.handles.residency_for_source("c1"),
1516 Some(&Residency::Resident)
1517 );
1518 }
1519
1520 #[test]
1521 fn autocompact_prunes_handles_for_archived_tool_results() {
1522 let mut mgr = ContextManager::new(1_000);
1523 for i in 0..30 {
1525 mgr.push_history(tool_result_msg(&format!("c{i}"), &"x".repeat(200)), 80);
1526 }
1527 assert_eq!(mgr.handles.all().len(), 30);
1528
1529 let (saved, _, archived, _) = mgr.compress(PressureAction::AutoCompact);
1530 assert!(saved > 0 && !archived.is_empty(), "expected archival");
1531
1532 let live_tool_results = mgr
1535 .partitions
1536 .history
1537 .messages
1538 .iter()
1539 .filter(|m| {
1540 matches!(&m.content, Content::Parts(p)
1541 if p.iter().any(|x| matches!(x, ContentPart::ToolResult { .. })))
1542 })
1543 .count();
1544 assert_eq!(mgr.handles.all().len(), live_tool_results);
1545 assert!(
1546 mgr.handles.all().len() < 30,
1547 "table must shrink with archival"
1548 );
1549 }
1550
1551 #[test]
1552 fn renew_prunes_handles_for_dropped_history() {
1553 let mut mgr = ContextManager::new(1_000);
1554 mgr.init_task("g".to_string(), vec![]);
1555 for i in 0..20 {
1556 mgr.push_history(tool_result_msg(&format!("c{i}"), "data"), 60);
1557 }
1558 mgr.renew();
1559 for h in mgr.handles.all() {
1561 if let Some(src) = h.source.as_ref() {
1562 assert!(
1563 mgr.handles.residency_for_source(src).is_some(),
1564 "no dangling handle survives renewal"
1565 );
1566 }
1567 }
1568 assert!(mgr.handles.all().len() <= 20);
1569 }
1570
1571 #[test]
1574 fn knowledge_budget_uses_stable_order_for_equal_value_and_warns_once() {
1575 let mut mgr = ContextManager::new(100);
1578 mgr.push_knowledge(CoreMessage::system("oldest unkeyed"), 10);
1579 mgr.push_knowledge_entry(Some("a".into()), CoreMessage::system("keyed"), 10, false);
1580 mgr.push_knowledge_entry(Some("p".into()), CoreMessage::system("pinned"), 10, true);
1581 mgr.push_knowledge_entry(
1582 Some("skill:x".into()),
1583 CoreMessage::system("skill"),
1584 10,
1585 false,
1586 );
1587
1588 let warn = mgr.enforce_knowledge_budget();
1589 assert_eq!(warn, Some((40, 25)));
1590 let e = &mgr.partitions.knowledge.entries;
1592 assert!(e[0].evict_at_boundary);
1593 assert!(e[1].evict_at_boundary);
1594 assert!(!e[2].evict_at_boundary, "pinned exempt");
1595 assert!(!e[3].evict_at_boundary, "skill pin exempt");
1596
1597 assert_eq!(mgr.enforce_knowledge_budget(), None);
1599
1600 let sweep = mgr.partitions.knowledge.sweep_at_boundary();
1602 assert_eq!(sweep.tokens_freed, 20);
1603 assert_eq!(mgr.partitions.knowledge.token_count, 20);
1604 assert_eq!(mgr.enforce_knowledge_budget(), None);
1606 }
1607
1608 #[test]
1609 fn knowledge_budget_warning_stands_when_only_exempt_weight_remains() {
1610 let mut mgr = ContextManager::new(100);
1611 mgr.push_knowledge_entry(
1612 Some("p".into()),
1613 CoreMessage::system("pinned heavy"),
1614 30,
1615 true,
1616 );
1617 mgr.push_knowledge_entry(
1618 Some("skill:x".into()),
1619 CoreMessage::system("skill heavy"),
1620 30,
1621 false,
1622 );
1623
1624 assert_eq!(mgr.enforce_knowledge_budget(), Some((60, 25)));
1626 assert!(
1627 mgr.partitions
1628 .knowledge
1629 .entries
1630 .iter()
1631 .all(|e| !e.evict_at_boundary)
1632 );
1633 }
1634
1635 #[test]
1636 fn knowledge_budget_retains_old_referenced_entry_over_new_irrelevant_entry() {
1637 let mut mgr = ContextManager::new(100);
1638 mgr.push_knowledge_entry(
1639 Some("project:orchid".into()),
1640 CoreMessage::system("ORCHID uses the Atlas storage engine"),
1641 10,
1642 false,
1643 );
1644 mgr.push_history(CoreMessage::user("For project:orchid keep using Atlas"), 5);
1646 mgr.push_knowledge_entry(
1647 Some("project:new".into()),
1648 CoreMessage::system("unrelated fresh material"),
1649 10,
1650 false,
1651 );
1652 mgr.push_knowledge_entry(
1653 Some("project:other".into()),
1654 CoreMessage::system("another unused reference"),
1655 10,
1656 false,
1657 );
1658
1659 assert_eq!(mgr.enforce_knowledge_budget(), Some((30, 25)));
1660 let entries = &mgr.partitions.knowledge.entries;
1661 assert!(
1662 !entries[0].evict_at_boundary,
1663 "a real reference must raise retention"
1664 );
1665 assert!(
1666 entries[1].evict_at_boundary,
1667 "lowest-value entry evicts first"
1668 );
1669 assert!(
1670 !entries[2].evict_at_boundary,
1671 "one eviction is enough to fit"
1672 );
1673 }
1674
1675 #[test]
1676 fn knowledge_budget_ratio_zero_disables() {
1677 let mut mgr = ContextManager::new(100);
1678 mgr.config.knowledge_budget_ratio = 0.0;
1679 mgr.push_knowledge(CoreMessage::system("huge"), 90);
1680 assert_eq!(mgr.enforce_knowledge_budget(), None);
1681 assert!(!mgr.partitions.knowledge.entries[0].evict_at_boundary);
1682 }
1683
1684 #[test]
1685 fn provider_and_output_reservations_reduce_the_hard_input_budget() {
1686 use crate::context::config::PromptBudgetConfig;
1687
1688 let mut mgr = ContextManager::new(100);
1689 mgr.set_prompt_budget(PromptBudgetConfig {
1690 prompt_overhead_tokens: 20,
1691 output_reserve_tokens: 20,
1692 safety_margin_tokens: 10,
1693 });
1694 mgr.partitions.system.push(
1702 CoreMessage::system(
1703 "System policy directive number seven requires strict adherence. ".repeat(10),
1704 ),
1705 60,
1706 );
1707
1708 let rendered = mgr.render();
1709 let overflow = rendered
1710 .budget_overflow
1711 .expect("fixed context exceeds input allowance");
1712 assert_eq!(overflow.max_tokens, 50);
1713 assert!(overflow.required_tokens > overflow.max_tokens);
1714 }
1715}