loopsmith_core/config/context.rs
1//! What each iteration is allowed to remember.
2//!
3//! A loop that re-sends every prior episode grows its own prompt without bound
4//! and bills accordingly; a loop that sends nothing produces the byte-identical
5//! prompt it already failed with. Neither is acceptable for a run measured in
6//! weeks, so each iteration is compressed to a summary and only the last few
7//! summaries are carried forward.
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(deny_unknown_fields)]
13pub struct ContextPolicy {
14 /// How many previous iteration summaries a node's prompt carries.
15 ///
16 /// `0` disables carry-forward entirely. The default of 2 is enough for a
17 /// node to see what it just tried and what it tried before that, which is
18 /// what "do not repeat yourself" needs, without the prompt growing with the
19 /// run.
20 #[serde(default = "default_carry")]
21 pub carry_summaries: usize,
22 /// Provider id used to write the optional narrative half of a summary.
23 ///
24 /// Omit it and summaries are still written — the deterministic facts are
25 /// always there. This only buys prose, and prose costs tokens every
26 /// iteration, so it is opt-in.
27 #[serde(default)]
28 pub summary_provider: Option<String>,
29 /// Ceiling on the narrative, in characters. A summary that grows without
30 /// limit defeats the purpose of having one.
31 #[serde(default = "default_max_chars")]
32 pub max_summary_chars: usize,
33}
34
35impl Default for ContextPolicy {
36 fn default() -> Self {
37 Self {
38 carry_summaries: default_carry(),
39 summary_provider: None,
40 max_summary_chars: default_max_chars(),
41 }
42 }
43}
44
45fn default_carry() -> usize {
46 2
47}
48fn default_max_chars() -> usize {
49 1200
50}