Skip to main content

lean_ctx/core/config/
model.rs

1use serde::{Deserialize, Serialize};
2
3#[allow(clippy::wildcard_imports)]
4use super::*;
5/// Global lean-ctx configuration loaded from `config.toml`, merged with project-local overrides.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(default)]
8pub struct Config {
9    pub ultra_compact: bool,
10    #[serde(default, deserialize_with = "serde_defaults::deserialize_tee_mode")]
11    pub tee_mode: TeeMode,
12    /// Verbosity of the reactive recovery footer on compressed output
13    /// (`off|minimal|full`, default `minimal`). See [`RecoveryHints`].
14    #[serde(default)]
15    pub recovery_hints: RecoveryHints,
16    #[serde(default)]
17    pub output_density: OutputDensity,
18    pub checkpoint_interval: u32,
19    pub excluded_commands: Vec<String>,
20    pub passthrough_urls: Vec<String>,
21    pub custom_aliases: Vec<AliasEntry>,
22    /// Output formats that are already compact/token-oriented and must be
23    /// preserved verbatim instead of being recompressed (#342). Matched against
24    /// the *output shape* (not the command name), so any tool emitting the
25    /// format is covered without enumerating commands in `excluded_commands`.
26    /// Default: `["toon"]`. Set to `[]` to disable and always recompress.
27    #[serde(default = "serde_defaults::default_preserve_compact_formats")]
28    pub preserve_compact_formats: Vec<String>,
29    /// Opt-in: apply the lossless JSON crusher to *verbatim* data commands
30    /// (`gh api`, `jq`, `kubectl get -o json`, `curl` JSON). Off by default, so
31    /// those outputs stay byte-for-byte verbatim. When on, an array-heavy JSON
32    /// payload the crusher can at least halve is reshaped into a compact, fully
33    /// reconstructible form; everything else stays verbatim. See
34    /// [`Config::crush_verbatim_json_enabled`] (#936).
35    #[serde(default)]
36    pub crush_verbatim_json: bool,
37    /// Commands taking longer than this threshold (ms) are recorded in the slow log.
38    /// Set to 0 to disable slow logging.
39    pub slow_command_threshold_ms: u64,
40    #[serde(default = "serde_defaults::default_theme")]
41    pub theme: String,
42    /// Anonymous opt-in telemetry heartbeat (version, OS, arch — no code/PII).
43    #[serde(default)]
44    pub telemetry: TelemetryConfig,
45    #[serde(default)]
46    pub cloud: CloudConfig,
47    #[serde(default)]
48    pub gain: GainConfig,
49    /// Model declaration for measured-vs-estimated cost reporting (MCP-only IDEs).
50    #[serde(default)]
51    pub cost: CostConfig,
52    /// Code-health engine: cognitive complexity, naming, coupling, edit-gate.
53    #[serde(default)]
54    pub code_health: CodeHealthConfig,
55    #[serde(default)]
56    pub autonomy: AutonomyConfig,
57    #[serde(default)]
58    pub providers: ProvidersConfig,
59    #[serde(default)]
60    pub proxy: ProxyConfig,
61    /// Conversation-history compression (`[conversation]`, opt-in; #1123).
62    #[serde(default)]
63    pub conversation: ConversationConfig,
64    /// Proxy-layer response shaping (`[response_shaping]`, #1125).
65    #[serde(default)]
66    pub response_shaping: ResponseShapingConfig,
67    #[serde(default)]
68    pub ocla: OclaConfig,
69    /// Generalized L1/L2/L3 cache settings (`[cache]`).
70    #[serde(default)]
71    pub cache: CacheConfig,
72    #[serde(default)]
73    pub agents: sections::AgentsConfig,
74    /// Whether the API proxy is enabled. Tri-state:
75    /// - None: undecided (fresh install, will prompt on interactive setup)
76    /// - Some(true): user opted in, proxy managed by lean-ctx
77    /// - Some(false): user opted out, never touch proxy or endpoints
78    #[serde(default)]
79    pub proxy_enabled: Option<bool>,
80    #[serde(default)]
81    pub proxy_port: Option<u16>,
82    /// Proxy reachability timeout in milliseconds. Default: 200.
83    /// Override via LEAN_CTX_PROXY_TIMEOUT_MS env var.
84    #[serde(default)]
85    pub proxy_timeout_ms: Option<u64>,
86    /// Strict proxy auth: when true, authenticate ONLY via the Bearer token
87    /// (`LEAN_CTX_PROXY_TOKEN`) and disable the provider-API-key fallback. Default
88    /// false keeps the loopback-friendly behavior where any local AI tool's own
89    /// provider key authenticates (the proxy never injects upstream credentials —
90    /// it forwards the caller's key verbatim). Enable on shared/multi-user hosts to
91    /// require the token; clients must then send `Authorization: Bearer [REDACTED:Authorization header]
92    #[serde(default)]
93    pub proxy_require_token: bool,
94    /// Skip ALL proxy authentication on loopback-bound listeners (#755).
95    /// When true **and** the proxy binds a loopback address, every request is
96    /// accepted without a Bearer token or provider API key — MCP clients,
97    /// browser dashboards, and CLI tools all work without auth setup.
98    /// Ignored on non-loopback binds (gateway mode always requires auth).
99    /// Env override: `LEAN_CTX_PROXY_LOOPBACK_OPEN`.
100    #[serde(default)]
101    pub proxy_loopback_open: bool,
102    /// Bind address for the proxy listener (gateway mode, enterprise#8).
103    /// Default `None` = `127.0.0.1` — local-safe, nothing changes for existing
104    /// installs. Set `"0.0.0.0"` (or a specific interface IP) to serve a whole
105    /// org from one host; any non-loopback bind hard-disables the provider-key
106    /// auth fallback (Bearer token becomes mandatory) and enables the
107    /// `proxy_allowed_hosts` Host-header allowlist. Env override:
108    /// `LEAN_CTX_PROXY_BIND_HOST`. An unparseable value falls back to loopback,
109    /// never to an open bind.
110    #[serde(default)]
111    pub proxy_bind_host: Option<String>,
112    /// Host-header allowlist for a non-loopback proxy bind (gateway mode):
113    /// DNS-rebinding protection. Entries are hostnames or IPs without port
114    /// (e.g. `"gateway.example.com"`). Loopback names are always allowed.
115    /// Ignored (loopback-only guard, today's behavior) while the bind is
116    /// loopback. Empty + non-loopback bind = only loopback Host headers pass,
117    /// so configure this when exposing the gateway.
118    #[serde(default)]
119    pub proxy_allowed_hosts: Vec<String>,
120    /// Proxy-wide request rate limit in requests/second (token bucket, burst =
121    /// 2x). `None` (default) = unlimited on a loopback bind — today's behavior —
122    /// and 50 rps with burst 100 on a non-loopback bind (gateway mode ships a
123    /// sane floor, enterprise#37). `0` disables the limiter even in gateway
124    /// mode (explicit opt-out).
125    #[serde(default)]
126    pub proxy_max_rps: Option<u32>,
127    /// Require Bearer-token authentication for the dashboard. Default `true`:
128    /// the dashboard generates (or uses the pinned) token and rejects `/api/*`
129    /// and `/metrics` without it. Set to `false` to run the dashboard with **no
130    /// auth token** — useful for a local/Docker setup where managing a token is
131    /// inconvenient. No-auth mode is not unprotected: cross-origin and CSRF
132    /// attacks from a malicious local website are blocked by request-header
133    /// validation instead (`Sec-Fetch-Site`, `Origin`/`Host` same-origin, and a
134    /// `Host` allowlist against DNS rebinding — see `dashboard::no_auth_request_ok`).
135    /// Override per-run via the `--no-auth` / `--auth=<bool>` flag or the
136    /// `LEAN_CTX_DASHBOARD_AUTH` env var.
137    #[serde(default = "serde_defaults::default_true")]
138    pub dashboard_auth: bool,
139    /// Provider prompt-cache hit rate for net-of-injection calculation (#1104).
140    /// Anthropic ~90%, OpenAI ~50%. Default 0.75 (conservative cross-provider).
141    #[serde(default)]
142    pub dashboard_cache_hit_rate: Option<f64>,
143    #[serde(default = "serde_defaults::default_buddy_enabled")]
144    pub buddy_enabled: bool,
145    #[serde(default = "serde_defaults::default_true")]
146    pub enable_wakeup_ctx: bool,
147    #[serde(default)]
148    pub redirect_exclude: Vec<String>,
149    /// Tools to exclude from the MCP tool list returned by list_tools.
150    /// Accepts exact tool names (e.g. `["ctx_graph", "ctx_agent"]`).
151    /// Empty by default — all tools listed, no behaviour change.
152    #[serde(default)]
153    pub disabled_tools: Vec<String>,
154    /// Prefer the host agent's native editor over lean-ctx edit operations (#454).
155    /// When true, the lean-ctx edit tool(s) (see [`EDIT_TOOL_NAMES`]) are neither
156    /// advertised in `list_tools` nor dispatchable (direct or via `ctx_call`), so
157    /// the agent falls back to the host's built-in editing UI. Reads / search /
158    /// shell / memory tools are unaffected. Override via
159    /// `LEAN_CTX_PREFER_NATIVE_EDITOR=1`.
160    #[serde(default)]
161    pub prefer_native_editor: bool,
162    /// Tool categories to activate by default for dynamic-tool-capable clients.
163    /// Values: "core" (always on), "arch", "debug", "memory", "metrics", "session".
164    /// Example: `default_tool_categories = ["core", "arch", "memory"]`
165    /// Override via LCTX_DEFAULT_CATEGORIES env var (comma-separated).
166    /// Empty = lean-ctx default (core + session).
167    #[serde(default)]
168    pub default_tool_categories: Vec<String>,
169    /// Disable all automatic read-mode degradation (auto_degrade + context_gate pressure).
170    /// When true, lean-ctx never downgrades requested read modes regardless of pressure.
171    /// Override via LCTX_NO_DEGRADE=1 env var.
172    #[serde(default)]
173    pub no_degrade: bool,
174    /// Serve explicit `full`/`lines:N-M` re-reads of session-cached files as
175    /// deltas: when the file changed on disk since it was cached, the read
176    /// returns `mode=diff` instead of re-emitting content the model already
177    /// holds. First reads are unaffected; `fresh=true` always bypasses.
178    /// Opt-in. Override via LCTX_DELTA_EXPLICIT=1/0 env var.
179    #[serde(default)]
180    pub delta_explicit: bool,
181    /// Persistent profile name. Checked after LEAN_CTX_PROFILE env var.
182    /// Set via `lean-ctx config set profile passthrough` or editing config.toml.
183    #[serde(default)]
184    pub profile: Option<String>,
185    /// Named configuration overlay selected from `[profiles.<name>]`.
186    /// `LEAN_CTX_CONFIG_PROFILE` takes precedence over this persisted selector.
187    #[serde(default)]
188    pub config_profile: Option<String>,
189    /// Partial configuration overlays keyed by profile name. Each overlay is
190    /// recursively merged over the base configuration at load time.
191    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
192    pub profiles: std::collections::BTreeMap<String, toml::Table>,
193    /// Tool visibility profile: "minimal" (5), "standard" (15), or "power" (all).
194    /// Override via LEAN_CTX_TOOL_PROFILE env var.
195    /// Existing installs default to "power" (backward compat).
196    #[serde(default)]
197    pub tool_profile: Option<String>,
198    /// Explicit list of enabled tool names. Used only when no tool_profile is pinned (tool_profile takes precedence); leave tool_profile unset to apply this list.
199    /// The universal invoker `ctx_call` stays advertised so unlisted tools remain
200    /// reachable — add `ctx_call` to `disabled_tools` to make this allowlist authoritative.
201    /// Example: `tools_enabled = ["ctx_read", "ctx_shell", "ctx_search"]`
202    #[serde(default)]
203    pub tools_enabled: Vec<String>,
204    /// Active context persona (`persona-spec-v1`). Selects the domain bundle —
205    /// tool surface, read-mode/compressor/chunker defaults, intent taxonomy,
206    /// sensitivity floor. Override via `LEAN_CTX_PERSONA`. Defaults to `coding`.
207    #[serde(default)]
208    pub persona: Option<String>,
209    #[serde(default)]
210    pub loop_detection: LoopDetectionConfig,
211    /// Controls where lean-ctx installs agent rule files.
212    /// Values: "both" (default), "global" (home-dir only), "project" (repo-local only).
213    /// Override via LEAN_CTX_RULES_SCOPE env var.
214    #[serde(default)]
215    pub rules_scope: Option<String>,
216    /// Controls how rules are injected for shared-instruction-file agents.
217    /// Values: "shared" (default, marker block in CLAUDE.md/CODEBUDDY.md/AGENTS.md/GEMINI.md),
218    /// "dedicated" (never touch those files; use each agent's config-driven
219    /// auto-load: SessionStart hook / instructions[] / context.fileName, #343), or
220    /// "off" (write no rules file at all — for hosts that supply their own
221    /// tool-steering workflow or phase-isolated/non-caching harnesses, #361).
222    /// Override via LEAN_CTX_RULES_INJECTION env var.
223    #[serde(default)]
224    pub rules_injection: Option<String>,
225    /// Mirror the host IDE's tool-permission rules onto lean-ctx's own MCP tools.
226    /// Values: "off" (default) or "on". When "on", lean-ctx reads the active
227    /// IDE's permission config (v1: OpenCode) and applies the equivalent
228    /// deny/ask/allow decision to the matching lean-ctx tool — so `ctx_shell`
229    /// honors your `bash`/`rm *` rules instead of bypassing them.
230    /// Override via LEAN_CTX_PERMISSION_INHERITANCE env var.
231    #[serde(default)]
232    pub permission_inheritance: Option<String>,
233    /// Extra glob patterns to ignore in graph/overview/preload (repo-local).
234    /// Example: `["externals/**", "target/**", "temp/**"]`
235    #[serde(default)]
236    pub extra_ignore_patterns: Vec<String>,
237    /// Controls agent output verbosity via instructions injection.
238    /// Values: "off" (default), "lite", "full", "ultra".
239    /// Override via LEAN_CTX_TERSE_AGENT env var.
240    #[serde(default)]
241    pub terse_agent: TerseAgent,
242    /// Unified compression level (replaces separate terse_agent + output_density).
243    /// Values: "off" (default), "lite", "standard", "max".
244    /// Override via LEAN_CTX_COMPRESSION env var.
245    #[serde(default)]
246    pub compression_level: CompressionLevel,
247    /// Science-driven cognitive features mode (#science).
248    #[serde(default)]
249    pub cognitive_mode: CognitiveMode,
250    /// Global compression intensity 0.0 (lossless) – 1.0 (max), mapped onto the
251    /// read modes / entropy / IB stages (see `core::aggressiveness`). `None`
252    /// (default) keeps each mode's built-in default. Override via the
253    /// `LEAN_CTX_AGGRESSIVENESS` env var or the `ctx_read` `aggressiveness` arg.
254    #[serde(default)]
255    pub compression_aggressiveness: Option<f64>,
256    /// Archive configuration for zero-loss compression.
257    #[serde(default)]
258    pub archive: ArchiveConfig,
259    /// Memory policy (knowledge/episodic/procedural/lifecycle budgets & thresholds).
260    #[serde(default)]
261    pub memory: MemoryPolicy,
262    /// Additional paths allowed by PathJail (absolute).
263    /// Useful for multi-project workspaces where the jail root is a parent directory.
264    /// Override via LEAN_CTX_ALLOW_PATH env var (path-list separator).
265    #[serde(default)]
266    pub allow_paths: Vec<String>,
267    /// Allow jailed tool access to home-level IDE config dirs (~/.cursor, VS Code,
268    /// Cline/Roo, JetBrains, …). Tri-state: `None` = not asked yet (setup prompts
269    /// once), `Some(false)` = declined, `Some(true)` = opted in. Those dirs can
270    /// expose other agents' sessions, MCP configs and credentials, so the effective
271    /// default is off. `~/.lean-ctx` (own data dir) is always allowed. The opt-in
272    /// set is registry-derived, covering every supported editor. Override via
273    /// LEAN_CTX_ALLOW_IDE_DIRS=1.
274    #[serde(default)]
275    pub allow_ide_config_dirs: Option<bool>,
276    /// Extra project roots for multi-root workspaces.
277    /// Tools like ctx_tree and ctx_search can scan across all roots in a single call.
278    /// These paths are automatically added to PathJail's allow-list.
279    /// Override via LEAN_CTX_EXTRA_ROOTS env var (path-list separator).
280    #[serde(default)]
281    pub extra_roots: Vec<String>,
282    /// Read-only roots: sibling subtrees the agent may READ but never WRITE.
283    /// Reads resolve as if they were extra_roots; every write tool (edit, refactor,
284    /// handoff/session export, memory compaction) is default-denied inside these
285    /// paths. Useful for reference repos mounted next to the project.
286    /// Override via LEAN_CTX_READ_ONLY_ROOTS env var (path-list separator).
287    #[serde(default)]
288    pub read_only_roots: Vec<String>,
289    /// Extra trusted roots OUTSIDE `$HOME` that lean-ctx may follow when an agent
290    /// config file/dir (`~/.claude.json`, `~/.codex/config.toml`, …) is a symlink
291    /// pointing there (#596). Empty by default → the strict `$HOME`-only boundary
292    /// stays in force (a planted symlink can never redirect a config write out of
293    /// the user's home, preserving the GL#442 symlink-hijack protection). Add a
294    /// parent like `/opt/dotfiles` only for a location you own and trust. Like
295    /// `extra_roots`, security-sensitive: stripped from untrusted project-local
296    /// configs. Override via LEAN_CTX_ALLOW_SYMLINK_ROOTS env var (path-list sep).
297    #[serde(default)]
298    pub allow_symlink_roots: Vec<String>,
299    /// Enable content-defined chunking (Rabin-Karp) for cache-optimal output ordering.
300    /// Stable chunks are emitted first to maximize prompt cache hits.
301    #[serde(default)]
302    pub content_defined_chunking: bool,
303    /// Skip session/knowledge/gotcha blocks in MCP instructions to minimize token overhead.
304    /// Override via LEAN_CTX_MINIMAL env var.
305    ///
306    /// Default `true` (deliberate): initialize-time instructions stay byte-stable
307    /// across sessions, which keeps the provider prompt-cache prefix warm (#498)
308    /// and holds the fixed per-session cost at the `doctor overhead --gate`
309    /// budget. Session continuity is NOT lost — the wakeup briefing (task,
310    /// findings, knowledge) is delivered through the first tool call's
311    /// `--- AUTO CONTEXT ---` block instead, which only bills when the agent
312    /// actually works. Set to `false` to additionally inject the ACTIVE SESSION
313    /// / PROJECT MEMORY blocks directly into the MCP `initialize` instructions.
314    #[serde(default)]
315    pub minimal_overhead: bool,
316    /// Opt-in: substitute long identifiers with short α-codes (+ a `§MAP` table)
317    /// in `aggressive` reads for projects with >50 source files. Off by default —
318    /// the abbreviated form is confusing for editing/refactoring, where the agent
319    /// needs the real package and symbol names. Enable for max exploration savings.
320    #[serde(default)]
321    pub symbol_map_auto: bool,
322    /// Opt-in: bias `auto` toward structure-first reads (`map`) for medium code
323    /// files on a cold read. Off by default — interactive sessions keep the
324    /// conservative `full` floor that avoids a follow-up body read. Enable for
325    /// phase-isolated harnesses (no warm-session cache payback), where a cold
326    /// `full` read is pure overhead and structure-first reads aid localization.
327    /// Override via the LEAN_CTX_STRUCTURE_FIRST env var.
328    #[serde(default)]
329    pub structure_first: bool,
330    /// Progressive disclosure for first-time reads (LCLM arXiv 2606.09659).
331    /// When true (default), large files default to compact overviews on first read:
332    ///     - Below progressive_threshold_lines: full content
333    ///     - Below progressive_signatures_max_lines: signatures mode
334    ///     - Above: map (manifest) mode
335    /// Models can always bypass with explicit mode= or lines= parameters.
336    /// Override via LEAN_CTX_PROGRESSIVE_DISCLOSURE env var.
337    #[serde(default = "serde_defaults::default_true")]
338    pub progressive_disclosure: bool,
339    /// Files with fewer lines than this threshold are always delivered in full.
340    /// Default: 100 lines.
341    #[serde(default = "serde_defaults::default_progressive_threshold_lines")]
342    pub progressive_threshold_lines: u32,
343    /// Files between threshold and this limit get signatures mode.
344    /// Files above get map (manifest) mode. Default: 500 lines.
345    #[serde(default = "serde_defaults::default_progressive_signatures_max")]
346    pub progressive_signatures_max: u32,
347    /// Opt-in: let the adaptive *learning* signals (predictor, bandit, heatmap,
348    /// adaptive policy, bounce/path memory) participate in `auto` mode
349    /// resolution. Off by default (#683): the default cascade is a deterministic
350    /// function of (file, task) — only capability guards and the size/task
351    /// heuristic decide — which keeps output byte-stable for provider prompt
352    /// caching (#498) and avoids per-read disk I/O from the learning stores.
353    /// Override via the LEAN_CTX_AUTO_MODE_LEARNING env var.
354    #[serde(default)]
355    pub auto_mode_learning: bool,
356    /// Team server URL for opt-in savings roll-up.
357    /// Set via `lean-ctx config set team_url https://...` or `[team] url` in config.toml.
358    /// Override via LEAN_CTX_TEAM_URL env var.
359    #[serde(default)]
360    pub team_url: Option<String>,
361    /// Bearer token for the team server (Authorization header on savings push /
362    /// pull). Set via `lean-ctx config set team_token <tok>` or `team_token` in
363    /// config.toml. Override via the LEAN_CTX_TEAM_TOKEN env var.
364    #[serde(default)]
365    pub team_token: Option<String>,
366    /// Opt-in: when true, the running daemon periodically pushes this machine's
367    /// signed savings batch to `team_url` so the team roll-up fills itself (no
368    /// manual `savings push` per dev). Off by default; requires `team_url` +
369    /// `team_token`. Set via `lean-ctx config set team_auto_push true`.
370    #[serde(default)]
371    pub team_auto_push: bool,
372    /// Enable human-readable activity journal (~/.lean-ctx/journal.md).
373    #[serde(default)]
374    pub journal_enabled: bool,
375    /// Opt-in: auto-persist interesting findings as knowledge facts.
376    #[serde(default)]
377    pub auto_capture: bool,
378    /// Hybrid search weights (BM25/dense/candidates).
379    #[serde(default)]
380    pub search: crate::core::hybrid_search::HybridConfig,
381    /// Code-graph settings, including traversal (co-access) edges (#289).
382    #[serde(default)]
383    pub graph: GraphConfig,
384    /// Index-time file filters (#735): include/exclude globs + gitignore
385    /// handling, applied by every index builder via `core::index_filter`.
386    #[serde(default)]
387    pub index: IndexConfig,
388    /// Skillify miner settings (#290): codify recurring patterns into rules.
389    #[serde(default)]
390    pub skillify: SkillifyConfig,
391    /// AI session-summary settings (#292): periodic, semantically-recallable summaries.
392    #[serde(default)]
393    pub summaries: SummariesConfig,
394    /// Optional LLM enhancement (query expansion, contradiction explanation).
395    #[serde(default)]
396    pub llm: crate::core::llm_enhance::LlmConfig,
397    /// Semantic-embedding engine settings (which local ONNX model to use).
398    #[serde(default)]
399    pub embedding: EmbeddingConfig,
400    /// Disable shell hook injection (the _lc() function that wraps CLI commands).
401    /// Override via LEAN_CTX_NO_HOOK env var.
402    #[serde(default)]
403    pub shell_hook_disabled: bool,
404    /// Shadow mode (default: true): denies native tools (Read/Grep/Shell) at
405    /// the permission level, forcing agents to use ctx_* MCP tools for maximum
406    /// compression. Without this, many harnesses silently prefer native tools,
407    /// negating lean-ctx's token savings. Disable with `shadow_mode = false`.
408    #[serde(default = "serde_defaults::default_true")]
409    pub shadow_mode: bool,
410    /// Global hook mode override. When set, overrides the per-agent auto-detection.
411    /// - `replace`: Native Read/Grep/Glob/Shell denied, lean-ctx MCP is the only path
412    /// - `hybrid`: MCP + shell hooks for compression (legacy)
413    /// - `mcp`: MCP server only, no hooks
414    ///
415    /// Default: unset (auto-detect per agent via `recommend_hook_mode`)
416    #[serde(default)]
417    pub hook_mode: Option<String>,
418    /// MCP tool surface mode. Controls how many tools `tools/list` advertises.
419    /// - `auto` (default): shadow-only (`ctx_call` only) for hook-covered
420    ///   clients, full lazy-core surface for all others.
421    /// - `mcp`: always advertise the full lazy-core/profile surface.
422    /// - `shadow`: always advertise only `ctx_call` (requires installed hooks).
423    #[serde(default)]
424    pub tool_surface: Option<String>,
425    /// Opt-in (#520): write a human-readable debug log of intercepted MCP tool
426    /// calls and hook routing decisions (lean-ctx vs native, with reasons) to
427    /// `<state_dir>/logs/debug.log`. Override via the LEAN_CTX_DEBUG_LOG env var.
428    #[serde(default)]
429    pub debug_log: bool,
430    /// Controls when the shell hook auto-activates aliases.
431    /// - `agents-only`: (Default since #699) Aliases only active when an AI
432    ///   agent env var is detected — transparent in plain human terminals.
433    /// - `always`: Aliases active in every interactive shell (pre-#699 default).
434    /// - `off`: Aliases never auto-activate (user must call `lean-ctx-on` manually).
435    ///
436    /// Override via `LEAN_CTX_SHELL_ACTIVATION` env var.
437    #[serde(default)]
438    pub shell_activation: ShellActivation,
439    /// Do not install agent CLI aliases (`claude`, `codex`, `gemini`,
440    /// `codebuddy`) into `~/.zshrc` / `~/.bashrc` during `onboard` / `setup`.
441    /// Existing alias blocks are removed when this is toggled on (#754).
442    /// Does NOT affect the shell compression hook (`_lc()`) — use
443    /// `shell_hook_disabled` for that. Orthogonal to `shell_activation` which
444    /// controls *when* aliases activate, not *whether* they are installed.
445    #[serde(default)]
446    pub skip_agent_aliases: bool,
447    /// Controls the native-Read → `ctx_read` redirect hook (#637).
448    /// - `auto`: (Default) redirect everywhere except hosts with a native
449    ///   read-before-write guard (Claude Code / CodeBuddy), where the path-swap
450    ///   would break native Write/Edit.
451    /// - `on`: always redirect (legacy behavior).
452    /// - `off`: never redirect native Read.
453    ///
454    /// Override via the `LEAN_CTX_READ_REDIRECT` env var.
455    #[serde(default)]
456    pub read_redirect: ReadRedirect,
457    /// Controls the PostToolUse native-Read re-read dedup (GL #1140).
458    /// - `auto`: (Default) replace only re-reads of unchanged files, and only on
459    ///   guard hosts (Claude Code / CodeBuddy) where the PreToolUse redirect is
460    ///   disabled — the guard-safe way to win the dedup savings back.
461    /// - `on`: dedup wherever the PostToolUse hook fires.
462    /// - `off`: never replace a Read result.
463    ///
464    /// Override via the `LEAN_CTX_READ_DEDUP` env var.
465    #[serde(default)]
466    pub read_dedup: ReadDedup,
467    /// Disable the daily version check against leanctx.com/version.txt.
468    /// Override via LEAN_CTX_NO_UPDATE_CHECK env var.
469    #[serde(default)]
470    pub update_check_disabled: bool,
471    #[serde(default)]
472    pub updates: UpdatesConfig,
473    /// Fixed-context budget accounting for `doctor overhead` / `gain` (#964).
474    #[serde(default)]
475    pub context: ContextConfig,
476    /// Maximum BM25 cache file size in MB. Indexes exceeding this are quarantined on load
477    /// and refused on save. Override via LEAN_CTX_BM25_MAX_CACHE_MB env var.
478    #[serde(default = "serde_defaults::default_bm25_max_cache_mb")]
479    pub bm25_max_cache_mb: u64,
480    /// Maximum number of files scanned by the lightweight JSON graph index.
481    /// 0 = unlimited (default). Set >0 to cap for constrained systems.
482    #[serde(default = "serde_defaults::default_graph_index_max_files")]
483    pub graph_index_max_files: u64,
484    /// Controls RAM vs feature trade-off. Values: "low", "balanced" (default), "performance".
485    /// Override via LEAN_CTX_MEMORY_PROFILE env var.
486    #[serde(default)]
487    pub memory_profile: MemoryProfile,
488    /// Controls how aggressively memory is freed when idle.
489    /// Values: "shared" (default, 1h TTL), "aggressive" (5 min TTL for low-memory devices).
490    /// Override via LEAN_CTX_MEMORY_CLEANUP env var.
491    #[serde(default)]
492    pub memory_cleanup: MemoryCleanup,
493    /// Soft process-RSS target as a percentage of system RAM (default: 5).
494    /// The guardian throttles and evicts above it, but this is not an OS hard cap.
495    /// Use a cgroup/container MemoryMax when strict isolation is required.
496    /// Override via LEAN_CTX_MAX_RAM_PERCENT env var.
497    #[serde(default = "serde_defaults::default_max_ram_percent")]
498    pub max_ram_percent: u8,
499    /// Simplified disk budget (MB). When set and detail values are at defaults,
500    /// distributes proportionally: archive=25%, bm25=10%, remainder for stores.
501    /// 0 = disabled (use individual settings). Override via LEAN_CTX_MAX_DISK_MB.
502    #[serde(default)]
503    pub max_disk_mb: u64,
504    /// Auto-purge data older than this many days. 0 = disabled.
505    /// Flows into archive.max_age_hours and lifecycle idle TTL.
506    #[serde(default)]
507    pub max_staleness_days: u32,
508    /// Cap on the rayon worker threads used by the CPU-heavy index build
509    /// (call graph etc.). 0 = rayon default (all cores). Set >0 to bound
510    /// per-instance CPU so a fleet of concurrent sessions can't saturate the
511    /// host on startup. Override via LEANCTX_INDEX_THREADS env var.
512    #[serde(default)]
513    pub max_index_threads: usize,
514    /// Controls visibility of token savings footers in tool output.
515    /// Values: "always" (default, show on every response), "never", "auto" (legacy compatibility).
516    /// Override via LEAN_CTX_SAVINGS_FOOTER or LEAN_CTX_SHOW_SAVINGS=1|0 env var.
517    #[serde(default)]
518    pub savings_footer: SavingsFooter,
519    /// Controls compression annotation style in savings footers.
520    /// Values: "quantized" (default, round to 10% buckets), "full" (exact %), "none" (suppress all).
521    /// Override via LEAN_CTX_COMPRESSION_ANNOTATION env var.
522    #[serde(default)]
523    pub compression_annotation: CompressionAnnotation,
524    /// Minimum savings percentage to emit a footer annotation. Below this threshold,
525    /// annotations are suppressed (the savings are too small to be worth the token cost).
526    /// Default: 5 (suppress annotations for savings below 5%).
527    #[serde(default = "serde_defaults::default_annotation_threshold_pct")]
528    pub annotation_threshold_pct: u8,
529    /// Maximum fresh tokens per single tool response (turn budget).
530    /// 0 = unlimited. Default: 4096. Prevents context bloat from oversized responses.
531    /// Override via LEAN_CTX_TURN_FRESH_LIMIT env var.
532    #[serde(default = "serde_defaults::default_turn_fresh_limit")]
533    pub turn_fresh_limit: usize,
534    /// Maximum cumulative fresh tokens per session. 0 = unlimited.
535    /// Default: 200000. Progressive compression kicks in at 50/75/90%.
536    /// Override via LEAN_CTX_SESSION_TOKEN_LIMIT env var.
537    #[serde(default = "serde_defaults::default_session_token_limit")]
538    pub session_token_limit: usize,
539    /// Explicit project root override. When set, lean-ctx uses this instead of auto-detection.
540    /// This prevents accidental home-directory scans when running from $HOME.
541    /// Override via LEAN_CTX_PROJECT_ROOT env var.
542    #[serde(default)]
543    pub project_root: Option<String>,
544    /// LSP server overrides. Map language name to custom binary path.
545    /// Example: `[lsp]\nrust = "/opt/rust-analyzer"\npython = "~/.venvs/main/bin/pylsp"`
546    #[serde(default)]
547    pub lsp: std::collections::HashMap<String, String>,
548    /// Per-IDE allowed paths. Restricts which directories lean-ctx will scan/index for each IDE.
549    /// Example: `[ide_paths]\ncursor = ["/home/user/projects/app1"]\ncodex = ["/home/user/codex"]`
550    /// When set, only these paths are indexed for the matching agent. Global `allow_paths` still applies.
551    #[serde(default)]
552    pub ide_paths: HashMap<String, Vec<String>>,
553    /// Custom model context window overrides.
554    /// Example: `[model_context_windows]\n"my-custom-model" = 500000`
555    #[serde(default)]
556    pub model_context_windows: HashMap<String, usize>,
557    /// Controls how much detail tool responses include.
558    ///
559    /// - `full` (default): complete compressed output
560    /// - `headers_only`: metadata line only (path, mode, token count)
561    ///
562    /// Override via `LEAN_CTX_RESPONSE_VERBOSITY` env var.
563    #[serde(default)]
564    pub response_verbosity: ResponseVerbosity,
565    /// Bypass hint mode. When agents use native Read/Grep instead of lean-ctx tools,
566    /// a hint is appended to the next tool response.
567    /// Values: "on" (default), "off", "aggressive" (hint on every call, no cooldown).
568    /// Override via LEAN_CTX_BYPASS_HINTS env var.
569    #[serde(default)]
570    pub bypass_hints: Option<String>,
571    /// Cache policy for ctx_read. Controls behavior on cache hits.
572    /// Values: "aggressive" (default, 13-tok stubs + compaction-aware reset),
573    /// "safe" (delivers map instead of stub), "off" (no caching, always disk read).
574    /// Override via LEAN_CTX_CACHE_POLICY env var.
575    #[serde(default)]
576    pub cache_policy: Option<String>,
577    /// Token budget for the in-memory `ctx_read` cache. When the cached total
578    /// plus an incoming read would exceed this, lean-ctx evicts the least-valuable
579    /// entries *immediately* (RRF: recency × frequency × size) so the read always
580    /// proceeds — eviction is never deferred to the staleness TTL. `0` uses the
581    /// built-in default (2M). `LEAN_CTX_CACHE_MAX_TOKENS` env var overrides this.
582    #[serde(default)]
583    pub cache_max_tokens: usize,
584    /// Cross-project boundary policy.
585    /// Controls whether cross-project search/import is allowed and whether access is audited.
586    #[serde(default)]
587    pub boundary_policy: crate::core::memory_boundary::BoundaryPolicy,
588    #[serde(default)]
589    pub secret_detection: SecretDetectionConfig,
590    /// Per-item sensitivity model with a uniform policy floor (#212).
591    /// Disabled by default → fully no-op until `sensitivity.enabled = true`.
592    #[serde(default)]
593    pub sensitivity: crate::core::sensitivity::SensitivityConfig,
594    /// MCP Tool-Catalog Gateway (#210): aggregate + query-route downstream MCP
595    /// servers. Global-only (never merged from project-local config) and a full
596    /// no-op until `gateway.enabled = true`.
597    #[serde(default)]
598    pub gateway: crate::core::mcp_catalog::GatewayConfig,
599    /// Self-hosted org gateway server (`[gateway_server]`, enterprise#20):
600    /// deployment parameters for the usage cockpit — seat count for the
601    /// org-wide projection, display label, and the central admin API the local
602    /// cockpit may read from. All optional; absent = local-only behavior.
603    #[serde(default)]
604    pub gateway_server: GatewayServerConfig,
605    /// Enterprise Suite connection (`[enterprise]`): connects this Runtime to
606    /// a LeanCTX Enterprise Gateway for economics tracking and model routing.
607    #[serde(default)]
608    pub enterprise: EnterpriseConfig,
609    /// Addon ecosystem security floor (#863): install policy, registry-signature
610    /// requirement and sandboxing for spawned addon servers. Global-only (never
611    /// merged from project-local config) and fully permissive by default.
612    #[serde(default)]
613    pub addons: crate::core::addons::AddonsConfig,
614    /// Allow automatic project-root re-rooting when absolute paths outside the jail are seen.
615    /// When false (default), absolute paths outside the jail are rejected without re-rooting.
616    /// Override via LEAN_CTX_ALLOW_REROOT env var.
617    #[serde(default)]
618    pub allow_auto_reroot: bool,
619    /// Verbatim binary path/expression for generated agent-hook commands
620    /// (#708). Users who sync agent settings (`~/.claude/settings.json`, …)
621    /// across machines with different usernames need an env-based form like
622    /// `$HOME/.local/bin/lean-ctx` — agent hosts run hook commands through a
623    /// shell, which expands it. When set (env `LEAN_CTX_HOOK_BINARY` wins,
624    /// then this key), every hook writer emits the value verbatim instead of
625    /// the machine-absolute exe path, so `init`/`doctor --fix`/`update` stop
626    /// rewriting synced files into sync ping-pong. Autostart plists/services
627    /// and daemon spawns are NOT affected — launchd/systemd do not expand
628    /// shell variables, so those keep the real absolute path. Empty (default)
629    /// = automatic absolute-path resolution (#367).
630    #[serde(default)]
631    pub hook_binary: Option<String>,
632    /// Disable PathJail entirely by setting `path_jail = false` in config.toml.
633    /// Useful in container/Docker environments where the sandbox is the boundary.
634    /// (The former `LEAN_CTX_NO_JAIL=1` env override was removed in v3.7.3.)
635    #[serde(default)]
636    pub path_jail: Option<bool>,
637    /// Sandbox level for code execution (ctx_exec).
638    /// 0 = subprocess only (current), 1 = OS-level restriction (Seatbelt/Landlock).
639    /// Override via LEAN_CTX_SANDBOX_LEVEL env var.
640    #[serde(default)]
641    pub sandbox_level: u8,
642    /// When true, large tool outputs (>4000 chars) are stored as references
643    /// and a short URI is returned instead of the full content.
644    /// Override via LEAN_CTX_REFERENCE_RESULTS env var.
645    #[serde(default)]
646    pub reference_results: bool,
647    /// Default per-agent token budget. 0 means unlimited.
648    /// Override per-agent via ctx_session or programmatically.
649    #[serde(default)]
650    pub agent_token_budget: usize,
651    /// Optional shell command allowlist. When non-empty, only commands whose base binary
652    /// is in this list are permitted by ctx_shell. Empty = disable allowlist (allow all).
653    /// Default includes common dev tools. Set to `[]` to disable.
654    /// Override via LEAN_CTX_SHELL_ALLOWLIST env var (comma-separated).
655    #[serde(default = "default_shell_allowlist")]
656    pub shell_allowlist: Vec<String>,
657
658    /// Extra commands MERGED on top of the effective `shell_allowlist` without replacing
659    /// the defaults. Setting `shell_allowlist` replaces the whole built-in list (a common
660    /// footgun); entries here are purely additive, which is what `lean-ctx allow <cmd>`
661    /// writes. Only applied in restricted mode (when the base allowlist is non-empty).
662    #[serde(default)]
663    pub shell_allowlist_extra: Vec<String>,
664
665    /// When true, block command substitution ($(), backticks) and process substitution
666    /// (<(), >()) in shell arguments. When false (default), only warn via tracing.
667    /// Default false preserves backward compatibility — set true for maximum security.
668    #[serde(default)]
669    pub shell_strict_mode: bool,
670
671    /// Shell-security mode for ctx_shell / `lean-ctx -c` command gating (GL #788):
672    /// `enforce` (default, secure), `warn` (run checks, log violations, never
673    /// block) or `off` (skip the allowlist + dangerous-pattern blocks entirely —
674    /// a deliberate opt-out; compression stays active). Override via
675    /// LEAN_CTX_SHELL_SECURITY. `None` resolves to `enforce`.
676    #[serde(default)]
677    pub shell_security: Option<String>,
678
679    /// Default shell-command timeout in seconds for *normal* commands. `None`
680    /// resolves to the built-in 2-minute default; heavy builds/tests use
681    /// [`Config::shell_heavy_timeout_secs`]. Override via
682    /// `LEAN_CTX_SHELL_TIMEOUT_SECS` (`LEAN_CTX_SHELL_TIMEOUT_MS` still wins over
683    /// both, in milliseconds).
684    #[serde(default)]
685    pub shell_timeout_secs: Option<u64>,
686
687    /// Shell-command timeout in seconds for *heavy* commands (cargo build/test,
688    /// make, docker build, git commit/push, …). `None` resolves to the built-in
689    /// 10-minute ceiling. Override via `LEAN_CTX_SHELL_HEAVY_TIMEOUT_SECS`.
690    #[serde(default)]
691    pub shell_heavy_timeout_secs: Option<u64>,
692
693    /// Extra command prefixes that get the heavy timeout ceiling. Merged with
694    /// the built-in list. Useful for project-specific long-running scripts.
695    /// Example: `shell_heavy_prefixes = ["python3 ", "./scripts/"]`
696    #[serde(default)]
697    pub shell_heavy_prefixes: Vec<String>,
698    /// When true, `ctx_shell` accepts shell file-write redirects (`>`, `>>`,
699    /// `tee`, heredoc-to-file, `curl -o`, `wget` default mode). Default false —
700    /// the native Write/Edit tool is preferred. Opt-in for power users who want
701    /// classic shell syntax; the real command gating (allowlist,
702    /// dangerous-pattern and interpreter-eval blocks) still applies. Override
703    /// via `LEAN_CTX_SHELL_ALLOW_WRITES=1`.
704    #[serde(default)]
705    pub shell_allow_writes: bool,
706    /// Absolute paths where shell redirects and `tee` may capture output.
707    /// Empty uses the operating system's temporary directories. Project files
708    /// remain denied even when a configured path overlaps the project root.
709    #[serde(default)]
710    pub write_allow_paths: Vec<String>,
711
712    /// #814: opt-in to allow `python3 -c`, `node -e`, etc. in ctx_shell.
713    /// Default `false` — inline code is blocked because it leaves no auditable
714    /// artifact. Override via `LEAN_CTX_SHELL_ALLOW_INLINE_SCRIPTS=1`.
715    #[serde(default)]
716    pub shell_allow_inline_scripts: bool,
717
718    /// Setup behavior: controls what gets injected during setup and updates.
719    #[serde(default)]
720    pub setup: SetupConfig,
721}