Skip to main content

zeph_tools/compression/
mod.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! TACO: self-evolving tool output compression.
5//!
6//! The core abstraction is [`OutputCompressor`], an async trait that post-processes
7//! raw tool output before it is injected into the LLM context. When disabled, the
8//! [`IdentityCompressor`] is wired in — its `compress` always returns `Ok(None)` and
9//! is zero-cost beyond one virtual call per tool output.
10//!
11//! ## Architecture
12//!
13//! - [`OutputCompressor`] — core async trait.
14//! - [`IdentityCompressor`] — default no-op.
15//! - [`RuleBasedCompressor`] — regex rules loaded from `SQLite`.
16//! - [`CompressedExecutor`] — decorator wrapping the root [`crate::ToolExecutor`].
17//! - [`CompressionRuleStore`] — SQLite/Postgres-backed rule persistence.
18//! - [`safe_compile`] — DoS-safe regex compilation with timeout.
19//!
20//! ## Invariants
21//!
22//! - **T1**: `IdentityCompressor` is the only compressor when
23//!   `[tools.compression] enabled = false`.
24//! - **T4**: Audit logging happens inside individual tool implementations, not inside
25//!   `CompressedExecutor`. Because compression wraps *outside* the tool boundary,
26//!   audit JSONL always records the raw pre-compression payload.
27
28mod identity;
29mod regex_safe;
30mod rule_based;
31mod store;
32
33pub mod decorator;
34
35pub use decorator::CompressedExecutor;
36pub use identity::IdentityCompressor;
37pub use regex_safe::safe_compile;
38pub use rule_based::RuleBasedCompressor;
39pub use store::{CompressionRule, CompressionRuleStore};
40
41use std::future::Future;
42use std::pin::Pin;
43
44use zeph_common::ToolName;
45
46/// Error variants for compression operations.
47#[derive(Debug, thiserror::Error)]
48pub enum CompressionError {
49    /// The rule's regex pattern string is syntactically invalid.
50    #[error("regex compile failed: {0}")]
51    BadPattern(String),
52    /// Regex compilation exceeded the configured deadline; the rule was skipped.
53    #[error("regex compile timed out")]
54    CompileTimeout,
55    /// A database error occurred while loading or persisting compression rules.
56    #[error(transparent)]
57    Db(#[from] zeph_db::SqlxError),
58}
59
60/// Core abstraction for tool output compression.
61///
62/// Returning `Ok(None)` means "pass through unchanged". Non-`None` `Ok(Some(s))`
63/// replaces the tool output with `s` before it reaches the LLM context.
64///
65/// Implementors must be `Send + Sync + Debug`.
66pub trait OutputCompressor: Send + Sync + std::fmt::Debug {
67    /// Attempt to compress `output` for the given `tool_name`.
68    ///
69    /// Returns `Ok(None)` when no rule matched or compression is not applicable.
70    /// Returns `Ok(Some(compressed))` with the replacement string.
71    ///
72    /// # Errors
73    ///
74    /// Returns [`CompressionError`] on internal failure. Errors are logged and the
75    /// raw output is used as fallback by [`CompressedExecutor`].
76    fn compress<'a>(
77        &'a self,
78        tool_name: &'a ToolName,
79        output: &'a str,
80    ) -> Pin<Box<dyn Future<Output = Result<Option<String>, CompressionError>> + Send + 'a>>;
81
82    /// Stable identifier for this compressor (used in logs).
83    fn name(&self) -> &'static str;
84}