atomcode_core/ctx/mod.rs
1//! Context construction strategies — one entry point per model/provider.
2//!
3//! This module owns the [`CtxBuilder`] trait (defined below) and the
4//! registry function [`for_provider`] (in [`resolver`]). Each concrete
5//! builder lives in its own file to keep [`mod.rs`](self) thin:
6//!
7//! | File | Role |
8//! |---------------|-------------------------------------------------------|
9//! | `mod.rs` | `CtxBuilder` trait definition + re-exports |
10//! | `resolver.rs` | `for_provider` dispatch (add new models here) |
11//! | `render.rs` | Default render / compression-plan policy |
12//! | `default.rs` | `DefaultCtx` — thin wrapper over `render` |
13//! | `ollama.rs` | `OllamaCtx` — small-window local models |
14//! | `truncate.rs` | Shared per-tool truncation helpers |
15//!
16//! Adding a new per-model ctx strategy:
17//!
18//! 1. Create `ctx/<name>.rs` with a `pub struct XxxCtx` and
19//! `impl CtxBuilder for XxxCtx`. Keep all ctor + tests + impl
20//! methods in that single file.
21//! 2. Declare `pub mod <name>;` below.
22//! 3. Register in `resolver::for_provider`.
23//!
24//! The trait is narrow on purpose: `build_messages` owns the full
25//! render path for its model, including any system-prompt variation,
26//! cold-zone handling, or tool-schema trimming. The shared
27//! [`render`] and [`truncate`] modules offer building blocks that
28//! impls may call or ignore at will.
29
30pub mod default;
31pub mod env;
32pub mod file_store;
33pub mod ollama;
34pub mod render;
35pub mod resolver;
36pub mod truncate;
37
38use crate::conversation::message::Message;
39use crate::conversation::{ContextStats, Conversation};
40use crate::tool::ToolResult;
41
42pub use default::DefaultCtx;
43pub use env::EnvSnapshot;
44pub use ollama::OllamaCtx;
45pub use resolver::for_provider;
46
47/// Per-session context construction strategy. Selected once at
48/// `AgentLoop::new` via [`for_provider`] and rebuilt on `ReloadConfig`.
49pub trait CtxBuilder: Send + Sync {
50 /// Build the messages array to send to the LLM for this turn.
51 ///
52 /// Implementations are free to:
53 /// - Transform `system_prompt` (strip tool schemas for small models,
54 /// add cache-friendly markers for Claude, replace entirely for
55 /// fine-tuned models)
56 /// - Choose any render pipeline (delegate to
57 /// [`crate::ctx::render::build_messages`], call shared helpers
58 /// directly, or roll their own)
59 /// - Decide how to handle `turn_reminder` — per-turn dynamic
60 /// context (git status, current task, prev edited files). The
61 /// default policy prepends it to the last User message for
62 /// system-prompt cache stability; a small-window model might
63 /// drop it to save tokens; a cache-sensitive model might insert
64 /// it as its own System message to keep the stable prefix clean.
65 /// Pass `""` when no reminder applies.
66 fn build_messages(
67 &self,
68 conv: &Conversation,
69 system_prompt: &str,
70 turn_reminder: &str,
71 ) -> (Vec<Message>, ContextStats);
72
73 /// Whether the conversation should be compressed. Default: never.
74 fn needs_compression(&self, _conv: &Conversation, _system_tokens: usize) -> bool {
75 false
76 }
77
78 /// Produce a compression plan `(content_to_summarize,
79 /// messages_to_remove)`. Default: `None` (no compression).
80 fn compression_plan(&self, _conv: &Conversation) -> Option<(String, usize)> {
81 None
82 }
83
84 /// Truncate a single tool output in place.
85 fn truncate_tool_output(&self, result: &mut ToolResult, tool_name: &str);
86
87 /// Effective token budget for this strategy.
88 ///
89 /// Reflects any defensive clamps the impl applies (e.g. `OllamaCtx`
90 /// floors at 4K even if `provider.context_window == 0`). Callers
91 /// that need the actual budget — `ctx_budget_hint` reset, datalog,
92 /// per-tool truncation — should use this instead of
93 /// `Config::default_context_window()`, which returns the raw,
94 /// unclamped value and may diverge for degenerate configs.
95 fn ctx_window(&self) -> usize;
96
97 /// Human-readable name for logging / debugging.
98 fn name(&self) -> &'static str;
99}