Skip to main content

zeph_config/memory/
fidelity.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Compression, forgetting, and fidelity configuration.
5//!
6//! ACON budget compaction, ARC compaction, typed pages, optical forgetting,
7//! eviction policy, pruning strategy, and compression guidelines.
8
9use crate::providers::ProviderName;
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12
13use super::CompactionProbeConfig;
14
15// ── ScrapMem optical forgetting config (issue #3713) ───────────────────────────
16
17/// `ScrapMem` optical forgetting configuration.
18///
19/// Controls progressive content-fidelity decay: `Full` → `Compressed` → `SummaryOnly`.
20/// The sweep is orthogonal to `SleepGate` (which decays importance scores); optical
21/// forgetting compresses content in place based on age.
22///
23/// # Example (TOML)
24///
25/// ```toml
26/// [memory.optical_forgetting]
27/// enabled = false
28/// compress_provider = ""
29/// compress_after_turns = 100
30/// summarize_after_turns = 500
31/// sweep_interval_secs = 3600
32/// sweep_batch_size = 50
33/// ```
34#[derive(Debug, Clone, Deserialize, Serialize)]
35#[serde(default)]
36pub struct OpticalForgettingConfig {
37    /// Enable optical forgetting sweep. Default: `false`.
38    pub enabled: bool,
39    /// Provider name from `[[llm.providers]]` for LLM-based content compression.
40    /// Falls back to the primary provider when empty.
41    pub compress_provider: ProviderName,
42    /// Number of conversation turns after which `Full` messages are compressed. Default: `100`.
43    pub compress_after_turns: u32,
44    /// Number of conversation turns after which `Compressed` messages become `SummaryOnly`. Default: `500`.
45    pub summarize_after_turns: u32,
46    /// How often the sweep runs, in seconds. Default: `3600`.
47    pub sweep_interval_secs: u64,
48    /// Maximum messages to compress per sweep iteration. Default: `50`.
49    pub sweep_batch_size: usize,
50}
51
52impl Default for OpticalForgettingConfig {
53    fn default() -> Self {
54        Self {
55            enabled: false,
56            compress_provider: ProviderName::default(),
57            compress_after_turns: 100,
58            summarize_after_turns: 500,
59            sweep_interval_secs: 3600,
60            sweep_batch_size: 50,
61        }
62    }
63}
64
65/// Session digest configuration (#2289).
66#[derive(Debug, Clone, Deserialize, Serialize)]
67#[serde(default)]
68pub struct DigestConfig {
69    /// Enable session digest generation at session end. Default: `false`.
70    pub enabled: bool,
71    /// Provider name from `[[llm.providers]]` for digest generation.
72    /// Falls back to the primary provider when `None`.
73    #[serde(default)]
74    pub provider: Option<ProviderName>,
75    /// Maximum tokens for the digest text. Default: `500`.
76    pub max_tokens: usize,
77    /// Maximum messages to feed into the digest prompt. Default: `50`.
78    pub max_input_messages: usize,
79}
80
81impl Default for DigestConfig {
82    fn default() -> Self {
83        Self {
84            enabled: false,
85            provider: None,
86            max_tokens: 500,
87            max_input_messages: 50,
88        }
89    }
90}
91
92/// Compression strategy for active context compression (#1161).
93#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
94#[serde(tag = "strategy", rename_all = "snake_case")]
95#[non_exhaustive]
96pub enum CompressionStrategy {
97    /// Compress only when reactive compaction fires (current behavior).
98    #[default]
99    Reactive,
100    /// Compress proactively when context exceeds `threshold_tokens`.
101    Proactive {
102        /// Token count that triggers proactive compression.
103        threshold_tokens: usize,
104        /// Maximum tokens for the compressed summary (passed to LLM as `max_tokens`).
105        max_summary_tokens: usize,
106    },
107    /// Agent calls `compress_context` tool explicitly. Reactive compaction still fires as a
108    /// safety net. The `compress_context` tool is also available in all other strategies.
109    Autonomous,
110    /// Knowledge-block-aware compression strategy (#2510).
111    ///
112    /// Low-relevance context segments are automatically consolidated into `AutoConsolidated`
113    /// knowledge blocks. LLM-curated blocks are never evicted before auto-consolidated ones.
114    Focus,
115}
116
117/// Pruning strategy for tool-output eviction inside the compaction pipeline (#1851, #2022).
118///
119/// When `context-compression` feature is enabled, this replaces the default oldest-first
120/// heuristic with scored eviction.
121#[derive(Debug, Clone, Copy, Default, Serialize, PartialEq, Eq)]
122#[serde(rename_all = "snake_case")]
123#[non_exhaustive]
124pub enum PruningStrategy {
125    /// Oldest-first eviction — current default behavior.
126    #[default]
127    Reactive,
128    /// Short LLM call extracts a task goal; blocks are scored by keyword overlap and pruned
129    /// lowest-first. Requires `context-compression` feature.
130    TaskAware,
131    /// Coarse-to-fine MIG scoring: relevance − redundancy with temporal partitioning.
132    /// Requires `context-compression` feature.
133    Mig,
134    /// Subgoal-aware pruning: tracks the agent's current subgoal via fire-and-forget LLM
135    /// extraction and partitions tool outputs into Active/Completed/Outdated tiers (#2022).
136    /// Requires `context-compression` feature.
137    Subgoal,
138    /// Subgoal-aware pruning combined with MIG redundancy scoring (#2022).
139    /// Requires `context-compression` feature.
140    SubgoalMig,
141}
142
143impl PruningStrategy {
144    /// Returns `true` when the strategy is subgoal-aware (`Subgoal` or `SubgoalMig`).
145    #[must_use]
146    pub fn is_subgoal(self) -> bool {
147        matches!(self, Self::Subgoal | Self::SubgoalMig)
148    }
149}
150
151// Route serde deserialization through FromStr so that removed variants (e.g. task_aware_mig)
152// emit a warning and fall back to Reactive instead of hard-erroring when found in TOML configs.
153impl<'de> serde::Deserialize<'de> for PruningStrategy {
154    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
155        let s = String::deserialize(deserializer)?;
156        s.parse().map_err(serde::de::Error::custom)
157    }
158}
159
160impl std::str::FromStr for PruningStrategy {
161    type Err = String;
162
163    fn from_str(s: &str) -> Result<Self, Self::Err> {
164        match s {
165            "reactive" => Ok(Self::Reactive),
166            "task_aware" | "task-aware" => Ok(Self::TaskAware),
167            "mig" => Ok(Self::Mig),
168            // task_aware_mig was removed (dead code — was routed to scored path only).
169            // Fall back to Reactive so existing TOML configs do not hard-error on startup.
170            "task_aware_mig" | "task-aware-mig" => {
171                tracing::warn!(
172                    "pruning strategy `task_aware_mig` has been removed; \
173                     falling back to `reactive`. Use `task_aware` or `mig` instead."
174                );
175                Ok(Self::Reactive)
176            }
177            "subgoal" => Ok(Self::Subgoal),
178            "subgoal_mig" | "subgoal-mig" => Ok(Self::SubgoalMig),
179            other => Err(format!(
180                "unknown pruning strategy `{other}`, expected \
181                 reactive|task_aware|mig|subgoal|subgoal_mig"
182            )),
183        }
184    }
185}
186
187fn default_high_density_budget() -> f32 {
188    0.7
189}
190
191fn default_low_density_budget() -> f32 {
192    0.3
193}
194
195/// Configuration for the `SleepGate` forgetting sweep (#2397).
196///
197/// When `enabled = true`, a background loop periodically decays importance scores
198/// (synaptic downscaling), restores recently-accessed memories (selective replay),
199/// and prunes memories below `forgetting_floor` (targeted forgetting).
200#[derive(Debug, Clone, Deserialize, Serialize)]
201#[serde(default)]
202pub struct ForgettingConfig {
203    /// Enable the `SleepGate` forgetting sweep. Default: `false`.
204    pub enabled: bool,
205    /// Per-sweep decay rate applied to importance scores. Range: (0.0, 1.0). Default: `0.1`.
206    pub decay_rate: f32,
207    /// Importance floor below which memories are pruned. Range: [0.0, 1.0]. Default: `0.05`.
208    pub forgetting_floor: f32,
209    /// How often the forgetting sweep runs, in seconds. Default: `7200`.
210    pub sweep_interval_secs: u64,
211    /// Maximum messages to process per sweep. Default: `500`.
212    pub sweep_batch_size: usize,
213    /// Hours: messages accessed within this window get replay protection. Default: `24`.
214    pub replay_window_hours: u32,
215    /// Messages with `access_count` >= this get replay protection. Default: `3`.
216    pub replay_min_access_count: u32,
217    /// Hours: never prune messages accessed within this window. Default: `24`.
218    pub protect_recent_hours: u32,
219    /// Never prune messages with `access_count` >= this. Default: `3`.
220    pub protect_min_access_count: u32,
221}
222
223impl Default for ForgettingConfig {
224    fn default() -> Self {
225        Self {
226            enabled: false,
227            decay_rate: 0.1,
228            forgetting_floor: 0.05,
229            sweep_interval_secs: 7200,
230            sweep_batch_size: 500,
231            replay_window_hours: 24,
232            replay_min_access_count: 3,
233            protect_recent_hours: 24,
234            protect_min_access_count: 3,
235        }
236    }
237}
238
239/// Configuration for active context compression (#1161).
240#[derive(Debug, Clone, Default, Deserialize, Serialize)]
241#[serde(default)]
242pub struct CompressionConfig {
243    /// Compression strategy.
244    #[serde(flatten)]
245    pub strategy: CompressionStrategy,
246    /// Tool-output pruning strategy (requires `context-compression` feature).
247    pub pruning_strategy: PruningStrategy,
248    /// Model to use for compression summaries.
249    ///
250    /// Currently unused — the primary summary provider is used regardless of this value.
251    /// Reserved for future per-compression model selection. Setting this field has no effect.
252    pub model: String,
253    /// Provider name from `[[llm.providers]]` for `compress_context` summaries.
254    /// Falls back to the primary provider when empty. Default: `""`.
255    pub compress_provider: ProviderName,
256    /// Compaction probe: validates summary quality before committing it (#1609).
257    #[serde(default)]
258    pub probe: CompactionProbeConfig,
259    /// Archive tool output bodies to `SQLite` before compaction (Memex #2432).
260    ///
261    /// When enabled, tool output bodies in the compaction range are saved to
262    /// `tool_overflow` with `archive_type = 'archive'` before summarization.
263    /// The LLM summarizes placeholder messages; archived content is appended as
264    /// a postfix after summarization so references survive compaction.
265    /// Default: `false`.
266    #[serde(default)]
267    pub archive_tool_outputs: bool,
268    /// Provider for Focus strategy segment scoring and the auto-consolidation extraction
269    /// LLM call (#2510, #3313). Both are cheap/mid-tier tasks, so one provider suffices.
270    /// Falls back to the primary provider when empty. Default: `""`.
271    pub focus_scorer_provider: ProviderName,
272    /// Token-budget fraction for high-density content in density-aware compression (#2481).
273    /// Must sum to 1.0 with `low_density_budget`. Default: `0.7`.
274    #[serde(default = "default_high_density_budget")]
275    pub high_density_budget: f32,
276    /// Token-budget fraction for low-density content in density-aware compression (#2481).
277    /// Must sum to 1.0 with `high_density_budget`. Default: `0.3`.
278    #[serde(default = "default_low_density_budget")]
279    pub low_density_budget: f32,
280    /// Typed-page classification and batch-level assertion checking (#3630).
281    #[serde(default)]
282    pub typed_pages: TypedPagesConfig,
283    /// Acon tool-result compression settings (#4021).
284    ///
285    /// Controls per-result and batch-level token budgets for tool outputs before they enter
286    /// message history. Distinct from `[tools.compression]` (TACO), which applies regex-based
287    /// rule compression at the executor level.
288    #[serde(default)]
289    pub acon: AconConfig,
290    /// ARC agent-initiated compaction settings (#4020).
291    ///
292    /// When `allow_agent_compaction = true`, the agent can call the `request_compaction`
293    /// internal tool to trigger context summarization on demand.
294    #[serde(default)]
295    pub arc: ArcCompactionConfig,
296}
297
298fn default_acon_passthrough_threshold() -> usize {
299    2000
300}
301
302fn default_acon_summarize_threshold() -> usize {
303    4000
304}
305
306fn default_acon_total_budget() -> usize {
307    8000
308}
309
310fn validate_acon_passthrough_threshold<'de, D>(deserializer: D) -> Result<usize, D::Error>
311where
312    D: serde::Deserializer<'de>,
313{
314    let value = <usize as serde::Deserialize>::deserialize(deserializer)?;
315    if value == 0 {
316        return Err(serde::de::Error::custom(
317            "acon.passthrough_threshold must be >= 1",
318        ));
319    }
320    Ok(value)
321}
322
323fn validate_acon_summarize_threshold<'de, D>(deserializer: D) -> Result<usize, D::Error>
324where
325    D: serde::Deserializer<'de>,
326{
327    let value = <usize as serde::Deserialize>::deserialize(deserializer)?;
328    if value == 0 {
329        return Err(serde::de::Error::custom(
330            "acon.summarize_threshold must be >= 1",
331        ));
332    }
333    Ok(value)
334}
335
336fn validate_acon_total_budget<'de, D>(deserializer: D) -> Result<usize, D::Error>
337where
338    D: serde::Deserializer<'de>,
339{
340    let value = <usize as serde::Deserialize>::deserialize(deserializer)?;
341    if value == 0 {
342        return Err(serde::de::Error::custom("acon.total_budget must be >= 1"));
343    }
344    Ok(value)
345}
346
347/// Token budget configuration for Acon tool-result compression (#4021).
348///
349/// Controls per-result and batch-level token budgets for tool outputs injected into context.
350/// Distinct from `[tools.compression]` (TACO), which applies regex-based rule compression
351/// at the executor level.
352///
353/// # Invariants
354///
355/// The following ordering must hold: `passthrough_threshold < summarize_threshold <= total_budget`.
356/// A config where `passthrough_threshold >= summarize_threshold` would make the summarization path
357/// unreachable, silently producing incorrect compression behavior.
358///
359/// # Example (TOML)
360///
361/// ```toml
362/// [memory.compression.acon]
363/// enabled = true
364/// passthrough_threshold = 2000
365/// summarize_threshold = 4000
366/// total_budget = 8000
367/// ```
368#[derive(Debug, Clone, Deserialize, Serialize)]
369#[serde(default)]
370pub struct AconConfig {
371    /// Enable Acon tool-result compression. Default: `true`.
372    pub enabled: bool,
373    /// Token count below which results pass through unchanged.
374    /// Also the truncation target: results above this get char-truncated to this size.
375    /// Must be < `summarize_threshold`. Default: `2000`.
376    #[serde(default = "default_acon_passthrough_threshold")]
377    #[serde(deserialize_with = "validate_acon_passthrough_threshold")]
378    pub passthrough_threshold: usize,
379    /// Token count above which LLM summarization should be attempted before truncation.
380    /// Must be > `passthrough_threshold` and <= `total_budget`. Default: `4000`.
381    #[serde(default = "default_acon_summarize_threshold")]
382    #[serde(deserialize_with = "validate_acon_summarize_threshold")]
383    pub summarize_threshold: usize,
384    /// Maximum total tokens for all tool results in a single turn.
385    /// Must be >= `summarize_threshold`. Default: `8000`.
386    #[serde(default = "default_acon_total_budget")]
387    #[serde(deserialize_with = "validate_acon_total_budget")]
388    pub total_budget: usize,
389    /// Provider name from `[[llm.providers]]` for LLM summarization of large results.
390    /// Falls back to the primary provider when empty. Default: `""`.
391    #[serde(default)]
392    pub summarize_provider: ProviderName,
393}
394
395impl AconConfig {
396    /// Validate threshold ordering invariants after deserialization.
397    ///
398    /// Returns an error string if `passthrough_threshold >= summarize_threshold` or
399    /// `summarize_threshold > total_budget`.
400    ///
401    /// # Errors
402    ///
403    /// Returns a descriptive error string when any threshold invariant is violated.
404    #[must_use = "validation result must be checked"]
405    pub fn validate(&self) -> Result<(), String> {
406        if self.passthrough_threshold >= self.summarize_threshold {
407            return Err(format!(
408                "acon: passthrough_threshold ({}) must be < summarize_threshold ({})",
409                self.passthrough_threshold, self.summarize_threshold
410            ));
411        }
412        if self.summarize_threshold > self.total_budget {
413            return Err(format!(
414                "acon: summarize_threshold ({}) must be <= total_budget ({})",
415                self.summarize_threshold, self.total_budget
416            ));
417        }
418        Ok(())
419    }
420}
421
422impl Default for AconConfig {
423    fn default() -> Self {
424        Self {
425            enabled: true,
426            passthrough_threshold: default_acon_passthrough_threshold(),
427            summarize_threshold: default_acon_summarize_threshold(),
428            total_budget: default_acon_total_budget(),
429            summarize_provider: ProviderName::default(),
430        }
431    }
432}
433
434/// Configuration for ARC agent-initiated compaction (#4020).
435///
436/// When `allow_agent_compaction = true`, the `request_compaction` internal tool is
437/// registered and the agent can call it to trigger context summarization on demand.
438/// Rate limiting is handled by `CompactionState` — only one compaction fires per turn.
439///
440/// # Example (TOML)
441///
442/// ```toml
443/// [memory.compression.arc]
444/// allow_agent_compaction = true
445/// ```
446#[derive(Debug, Clone, Deserialize, Serialize)]
447#[serde(default)]
448pub struct ArcCompactionConfig {
449    /// Allow the agent to request compaction via the `request_compaction` tool call.
450    /// Default: `true`.
451    pub allow_agent_compaction: bool,
452}
453
454impl Default for ArcCompactionConfig {
455    fn default() -> Self {
456        Self {
457            allow_agent_compaction: true,
458        }
459    }
460}
461
462/// Configuration for typed-page compaction invariants (#3630).
463///
464/// Controls classification, batch-level assertion checking, and audit logging.
465/// All behavior is disabled by default; set `enabled = true` to activate.
466///
467/// # Example (TOML)
468///
469/// ```toml
470/// [memory.compression.typed_pages]
471/// enabled = true
472/// enforcement = "active"
473/// audit_path = ""
474/// audit_channel_capacity = 256
475/// ```
476#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
477#[serde(default)]
478pub struct TypedPagesConfig {
479    /// Enable typed-page classification and batch-level assertion checking.
480    /// Default: `false`.
481    pub enabled: bool,
482    /// Enforcement mode:
483    ///
484    /// - `observe`: classify and emit audit records only; no behavioral change.
485    /// - `active`: classify + `SystemContext` pointer-replace + batch assertions + audit.
486    ///
487    /// Default: `"observe"`.
488    pub enforcement: TypedPagesEnforcement,
489    /// Path for JSONL audit log. Empty string resolves to `{data_dir}/audit/compaction.jsonl`.
490    /// Default: `""`.
491    ///
492    /// # Security
493    ///
494    /// This field is **operator-only trusted input** read from the agent's configuration file.
495    /// Write access to the config file implies file-system write access, so no additional
496    /// canonicalization is enforced here. Do not expose this field to end-users or untrusted
497    /// configuration sources.
498    pub audit_path: String,
499    /// Bounded channel capacity for the async audit writer. Default: `256`.
500    pub audit_channel_capacity: usize,
501}
502
503impl Default for TypedPagesConfig {
504    fn default() -> Self {
505        Self {
506            enabled: false,
507            enforcement: TypedPagesEnforcement::Observe,
508            audit_path: String::new(),
509            audit_channel_capacity: 256,
510        }
511    }
512}
513
514/// Enforcement mode for typed-page compaction (#3630).
515#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize, JsonSchema)]
516#[serde(rename_all = "snake_case")]
517#[non_exhaustive]
518pub enum TypedPagesEnforcement {
519    /// Classify and audit only. Zero behavioral change relative to the untyped path.
520    #[default]
521    Observe,
522    /// Classify + pointer-replace `SystemContext` pages + batch assertions + audit.
523    Active,
524}
525
526/// Time-based microcompact configuration (#2699).
527///
528/// When `enabled = true`, low-value tool outputs are cleared from context
529/// (replaced with a sentinel string) when the session gap exceeds `gap_threshold_minutes`.
530/// The most recent `keep_recent` tool messages are preserved unconditionally.
531#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
532#[serde(default)]
533pub struct MicrocompactConfig {
534    /// Enable time-based microcompaction. Default: `false`.
535    pub enabled: bool,
536    /// Minimum idle gap in minutes before stale tool outputs are cleared. Default: `60`.
537    pub gap_threshold_minutes: u32,
538    /// Number of most recent compactable tool messages to preserve. Default: `3`.
539    pub keep_recent: usize,
540}
541
542impl Default for MicrocompactConfig {
543    fn default() -> Self {
544        Self {
545            enabled: false,
546            gap_threshold_minutes: 60,
547            keep_recent: 3,
548        }
549    }
550}
551
552// ── Eviction config (moved from zeph-memory) ─────────────────────────────────
553
554/// Eviction policy variant.
555///
556/// Serialises as `"ebbinghaus"` in TOML/JSON so existing configs remain valid.
557#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
558#[serde(rename_all = "lowercase")]
559#[non_exhaustive]
560pub enum EvictionPolicy {
561    /// Ebbinghaus forgetting-curve eviction.
562    #[default]
563    Ebbinghaus,
564}
565
566/// Configuration for the memory eviction policy.
567///
568/// Controls which policy runs during the periodic sweep and how many entries
569/// are retained. `zeph-memory` re-exports this type from here.
570#[derive(Debug, Clone, Deserialize, Serialize)]
571pub struct EvictionConfig {
572    /// Eviction policy. Currently only [`EvictionPolicy::Ebbinghaus`] is supported.
573    pub policy: EvictionPolicy,
574    /// Maximum number of entries to retain. `0` means unlimited (eviction disabled).
575    pub max_entries: usize,
576    /// How often to run the eviction sweep, in seconds.
577    pub sweep_interval_secs: u64,
578}
579
580impl Default for EvictionConfig {
581    fn default() -> Self {
582        Self {
583            policy: EvictionPolicy::Ebbinghaus,
584            max_entries: 0,
585            sweep_interval_secs: 3600,
586        }
587    }
588}
589
590// ── Compression guidelines config (moved from zeph-memory) ───────────────────
591
592/// Configuration for ACON failure-driven compression guidelines.
593///
594/// `zeph-memory` re-exports this type from here.
595#[derive(Debug, Clone, Deserialize, Serialize)]
596#[serde(default)]
597pub struct CompressionGuidelinesConfig {
598    /// Enable the feature. Default: `false`.
599    pub enabled: bool,
600    /// Minimum unused failure pairs before triggering a guidelines update. Default: `5`.
601    pub update_threshold: u16,
602    /// Maximum token budget for the guidelines document. Default: `500`.
603    pub max_guidelines_tokens: usize,
604    /// Maximum failure pairs consumed per update cycle. Default: `10`.
605    pub max_pairs_per_update: usize,
606    /// Number of turns after hard compaction to watch for context loss. Default: `10`.
607    pub detection_window_turns: u64,
608    /// Interval in seconds between background updater checks. Default: `300`.
609    pub update_interval_secs: u64,
610    /// Maximum unused failure pairs to retain (cleanup policy). Default: `100`.
611    pub max_stored_pairs: usize,
612    /// Provider name from `[[llm.providers]]` for guidelines update LLM calls.
613    /// `None` (or `Some("")`) falls back to the primary provider.
614    #[serde(default, skip_serializing_if = "Option::is_none")]
615    pub guidelines_provider: Option<ProviderName>,
616    /// Maintain separate guideline documents per content category.
617    #[serde(default)]
618    pub categorized_guidelines: bool,
619}
620
621impl Default for CompressionGuidelinesConfig {
622    fn default() -> Self {
623        Self {
624            enabled: false,
625            update_threshold: 5,
626            max_guidelines_tokens: 500,
627            max_pairs_per_update: 10,
628            detection_window_turns: 10,
629            update_interval_secs: 300,
630            max_stored_pairs: 100,
631            guidelines_provider: None,
632            categorized_guidelines: false,
633        }
634    }
635}