Skip to main content

batty_cli/team/config/
types.rs

1//! Type definitions for team configuration.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Deserializer};
6
7use super::super::DEFAULT_EVENT_LOG_MAX_BYTES;
8
9#[derive(Debug, Clone)]
10pub struct TeamConfig {
11    pub name: String,
12    /// Team-level default agent backend. Individual roles can override this
13    /// with their own `agent` field. Resolution order:
14    /// role-level agent > team-level agent > "claude" (hardcoded default).
15    pub agent: Option<String>,
16    pub workflow_mode: WorkflowMode,
17    pub board: BoardConfig,
18    pub standup: StandupConfig,
19    pub automation: AutomationConfig,
20    pub automation_sender: Option<String>,
21    /// External senders (e.g. email-router, slack-bridge) that are allowed to
22    /// message any role even though they are not team members.
23    pub external_senders: Vec<String>,
24    pub orchestrator_pane: bool,
25    pub orchestrator_position: OrchestratorPosition,
26    pub layout: Option<LayoutConfig>,
27    pub workflow_policy: WorkflowPolicy,
28    pub cost: CostConfig,
29    pub grafana: GrafanaConfig,
30    /// When true, agents are spawned as shim subprocesses instead of
31    /// directly in tmux panes. The shim manages PTY, state classification,
32    /// and message delivery over a structured channel.
33    pub use_shim: bool,
34    /// When true and `use_shim` is enabled, agents that support structured I/O
35    /// (Claude Code, Codex) communicate via JSON protocols instead of PTY
36    /// screen-scraping. Requires `use_shim: true`. Defaults to true.
37    pub use_sdk_mode: bool,
38    /// When true and `use_shim` is enabled, crashed agents are automatically
39    /// respawned instead of escalating to the manager. This is the default
40    /// posture for unattended teams; disable it only for debugging or
41    /// deliberate manual supervision.
42    pub auto_respawn_on_crash: bool,
43    /// Interval in seconds between Ping health checks sent to shim handles.
44    pub shim_health_check_interval_secs: u64,
45    /// Seconds without a Pong response before a shim handle is considered stale.
46    pub shim_health_timeout_secs: u64,
47    /// Seconds to wait for graceful shutdown before sending Kill.
48    pub shim_shutdown_timeout_secs: u32,
49    /// Maximum seconds an agent can remain in "Working" state before being
50    /// force-transitioned to Idle. Prevents permanent stalls where the shim
51    /// state classifier gets stuck on "working" while the agent is actually
52    /// idle. 0 or None disables the check. Default: 1800 (30 minutes).
53    pub shim_working_state_timeout_secs: u64,
54    /// Maximum seconds a message can sit in the pending delivery queue before
55    /// being force-delivered via inbox fallback. Prevents message loss when
56    /// the target agent appears permanently busy. Default: 600 (10 minutes).
57    pub pending_queue_max_age_secs: u64,
58    pub event_log_max_bytes: u64,
59    pub retro_min_duration_secs: u64,
60    pub roles: Vec<RoleDef>,
61}
62
63#[derive(Debug, Deserialize)]
64struct TeamConfigWire {
65    pub name: String,
66    #[serde(default)]
67    pub agent: Option<String>,
68    #[serde(default)]
69    pub workflow_mode: Option<WorkflowMode>,
70    #[serde(default)]
71    pub board: BoardConfig,
72    #[serde(default)]
73    pub standup: StandupConfig,
74    #[serde(default)]
75    pub automation: AutomationConfig,
76    #[serde(default)]
77    pub automation_sender: Option<String>,
78    #[serde(default)]
79    pub external_senders: Vec<String>,
80    #[serde(default)]
81    pub orchestrator_pane: Option<bool>,
82    #[serde(default)]
83    pub orchestrator_position: OrchestratorPosition,
84    #[serde(default)]
85    pub layout: Option<LayoutConfig>,
86    #[serde(default)]
87    pub workflow_policy: WorkflowPolicy,
88    #[serde(default)]
89    pub cost: CostConfig,
90    #[serde(default)]
91    pub grafana: GrafanaConfig,
92    #[serde(default)]
93    pub use_shim: bool,
94    #[serde(default = "default_use_sdk_mode")]
95    pub use_sdk_mode: bool,
96    #[serde(default = "default_auto_respawn_on_crash")]
97    pub auto_respawn_on_crash: bool,
98    #[serde(default = "default_shim_health_check_interval_secs")]
99    pub shim_health_check_interval_secs: u64,
100    #[serde(default = "default_shim_health_timeout_secs")]
101    pub shim_health_timeout_secs: u64,
102    #[serde(default = "default_shim_shutdown_timeout_secs")]
103    pub shim_shutdown_timeout_secs: u32,
104    #[serde(default = "default_shim_working_state_timeout_secs")]
105    pub shim_working_state_timeout_secs: u64,
106    #[serde(default = "default_pending_queue_max_age_secs")]
107    pub pending_queue_max_age_secs: u64,
108    #[serde(default = "default_event_log_max_bytes")]
109    pub event_log_max_bytes: u64,
110    #[serde(default = "default_retro_min_duration_secs")]
111    pub retro_min_duration_secs: u64,
112    pub roles: Vec<RoleDef>,
113}
114
115impl From<TeamConfigWire> for TeamConfig {
116    fn from(wire: TeamConfigWire) -> Self {
117        let orchestrator_pane = wire
118            .orchestrator_pane
119            .unwrap_or_else(default_orchestrator_pane);
120        let workflow_mode = wire.workflow_mode.unwrap_or_else(|| {
121            if matches!(wire.orchestrator_pane, Some(true)) {
122                WorkflowMode::Hybrid
123            } else {
124                default_workflow_mode()
125            }
126        });
127
128        Self {
129            name: wire.name,
130            agent: wire.agent,
131            workflow_mode,
132            board: wire.board,
133            standup: wire.standup,
134            automation: wire.automation,
135            automation_sender: wire.automation_sender,
136            external_senders: wire.external_senders,
137            orchestrator_pane,
138            orchestrator_position: wire.orchestrator_position,
139            layout: wire.layout,
140            workflow_policy: wire.workflow_policy,
141            cost: wire.cost,
142            grafana: wire.grafana,
143            use_shim: wire.use_shim,
144            use_sdk_mode: wire.use_sdk_mode,
145            auto_respawn_on_crash: wire.auto_respawn_on_crash,
146            shim_health_check_interval_secs: wire.shim_health_check_interval_secs,
147            shim_health_timeout_secs: wire.shim_health_timeout_secs,
148            shim_shutdown_timeout_secs: wire.shim_shutdown_timeout_secs,
149            shim_working_state_timeout_secs: wire.shim_working_state_timeout_secs,
150            pending_queue_max_age_secs: wire.pending_queue_max_age_secs,
151            event_log_max_bytes: wire.event_log_max_bytes,
152            retro_min_duration_secs: wire.retro_min_duration_secs,
153            roles: wire.roles,
154        }
155    }
156}
157
158impl<'de> Deserialize<'de> for TeamConfig {
159    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
160    where
161        D: Deserializer<'de>,
162    {
163        TeamConfigWire::deserialize(deserializer).map(Into::into)
164    }
165}
166
167#[derive(Debug, Clone, Deserialize)]
168pub struct GrafanaConfig {
169    #[serde(default)]
170    pub enabled: bool,
171    #[serde(default = "default_grafana_port")]
172    pub port: u16,
173}
174
175impl Default for GrafanaConfig {
176    fn default() -> Self {
177        Self {
178            enabled: false,
179            port: default_grafana_port(),
180        }
181    }
182}
183
184fn default_grafana_port() -> u16 {
185    3000
186}
187
188fn default_use_sdk_mode() -> bool {
189    true
190}
191
192fn default_auto_respawn_on_crash() -> bool {
193    true
194}
195
196#[derive(Debug, Clone, Deserialize, Default)]
197pub struct CostConfig {
198    #[serde(default)]
199    pub models: HashMap<String, ModelPricing>,
200}
201
202#[derive(Debug, Clone, Deserialize)]
203pub struct ModelPricing {
204    pub input_usd_per_mtok: f64,
205    #[serde(default)]
206    pub cached_input_usd_per_mtok: f64,
207    #[serde(default)]
208    pub cache_creation_input_usd_per_mtok: Option<f64>,
209    #[serde(default)]
210    pub cache_creation_5m_input_usd_per_mtok: Option<f64>,
211    #[serde(default)]
212    pub cache_creation_1h_input_usd_per_mtok: Option<f64>,
213    #[serde(default)]
214    pub cache_read_input_usd_per_mtok: f64,
215    pub output_usd_per_mtok: f64,
216    #[serde(default)]
217    pub reasoning_output_usd_per_mtok: Option<f64>,
218}
219
220#[derive(Debug, Clone, Deserialize)]
221pub struct WorkflowPolicy {
222    #[serde(default)]
223    pub clean_room_mode: bool,
224    #[serde(default)]
225    pub barrier_groups: HashMap<String, Vec<String>>,
226    #[serde(default = "default_handoff_directory")]
227    pub handoff_directory: String,
228    #[serde(default)]
229    pub wip_limit_per_engineer: Option<u32>,
230    #[serde(default)]
231    pub wip_limit_per_reviewer: Option<u32>,
232    #[serde(default = "default_pipeline_starvation_threshold")]
233    pub pipeline_starvation_threshold: Option<usize>,
234    #[serde(default = "default_escalation_threshold_secs")]
235    pub escalation_threshold_secs: u64,
236    #[serde(default = "default_review_nudge_threshold_secs")]
237    pub review_nudge_threshold_secs: u64,
238    #[serde(default = "default_review_timeout_secs")]
239    pub review_timeout_secs: u64,
240    #[serde(default)]
241    pub review_timeout_overrides: HashMap<String, ReviewTimeoutOverride>,
242    #[serde(default)]
243    pub auto_archive_done_after_secs: Option<u64>,
244    #[serde(default)]
245    pub capability_overrides: HashMap<String, Vec<String>>,
246    #[serde(default = "default_stall_threshold_secs")]
247    pub stall_threshold_secs: u64,
248    #[serde(default = "default_max_stall_restarts")]
249    pub max_stall_restarts: u32,
250    #[serde(default = "default_health_check_interval_secs")]
251    pub health_check_interval_secs: u64,
252    #[serde(default = "default_planning_cycle_cooldown_secs")]
253    pub planning_cycle_cooldown_secs: u64,
254    #[serde(default = "default_narration_detection_threshold")]
255    pub narration_detection_threshold: usize,
256    #[serde(default = "default_context_pressure_threshold_bytes")]
257    pub context_pressure_threshold_bytes: u64,
258    #[serde(default = "default_context_pressure_restart_delay_secs")]
259    pub context_pressure_restart_delay_secs: u64,
260    #[serde(default = "default_graceful_shutdown_timeout_secs")]
261    pub graceful_shutdown_timeout_secs: u64,
262    #[serde(default = "default_auto_commit_on_restart")]
263    pub auto_commit_on_restart: bool,
264    #[serde(default = "default_uncommitted_warn_threshold")]
265    pub uncommitted_warn_threshold: usize,
266    #[serde(default)]
267    pub test_command: Option<String>,
268    #[serde(default)]
269    pub auto_merge: AutoMergePolicy,
270    /// When true, context exhaustion restarts capture a work summary and
271    /// inject it into the new agent session so it can continue where the
272    /// old session left off.
273    #[serde(default = "default_context_handoff_enabled")]
274    pub context_handoff_enabled: bool,
275    /// Number of PTY screen pages to include in the handoff summary.
276    #[serde(default = "default_handoff_screen_history")]
277    pub handoff_screen_history: usize,
278}
279
280/// Per-priority override for review timeout thresholds.
281/// When a task's priority matches a key in `review_timeout_overrides`,
282/// these values replace the global defaults.
283#[derive(Debug, Clone, Deserialize)]
284pub struct ReviewTimeoutOverride {
285    /// Nudge threshold override (seconds). Falls back to global if absent.
286    pub review_nudge_threshold_secs: Option<u64>,
287    /// Escalation threshold override (seconds). Falls back to global if absent.
288    pub review_timeout_secs: Option<u64>,
289}
290
291impl Default for WorkflowPolicy {
292    fn default() -> Self {
293        Self {
294            clean_room_mode: false,
295            barrier_groups: HashMap::new(),
296            handoff_directory: default_handoff_directory(),
297            wip_limit_per_engineer: None,
298            wip_limit_per_reviewer: None,
299            pipeline_starvation_threshold: default_pipeline_starvation_threshold(),
300            escalation_threshold_secs: default_escalation_threshold_secs(),
301            review_nudge_threshold_secs: default_review_nudge_threshold_secs(),
302            review_timeout_secs: default_review_timeout_secs(),
303            review_timeout_overrides: HashMap::new(),
304            auto_archive_done_after_secs: None,
305            capability_overrides: HashMap::new(),
306            stall_threshold_secs: default_stall_threshold_secs(),
307            max_stall_restarts: default_max_stall_restarts(),
308            health_check_interval_secs: default_health_check_interval_secs(),
309            planning_cycle_cooldown_secs: default_planning_cycle_cooldown_secs(),
310            narration_detection_threshold: default_narration_detection_threshold(),
311            context_pressure_threshold_bytes: default_context_pressure_threshold_bytes(),
312            context_pressure_restart_delay_secs: default_context_pressure_restart_delay_secs(),
313            graceful_shutdown_timeout_secs: default_graceful_shutdown_timeout_secs(),
314            auto_commit_on_restart: default_auto_commit_on_restart(),
315            uncommitted_warn_threshold: default_uncommitted_warn_threshold(),
316            test_command: None,
317            auto_merge: AutoMergePolicy::default(),
318            context_handoff_enabled: default_context_handoff_enabled(),
319            handoff_screen_history: default_handoff_screen_history(),
320        }
321    }
322}
323
324fn default_graceful_shutdown_timeout_secs() -> u64 {
325    5
326}
327
328fn default_auto_commit_on_restart() -> bool {
329    true
330}
331
332fn default_context_handoff_enabled() -> bool {
333    true
334}
335
336fn default_handoff_screen_history() -> usize {
337    20
338}
339
340fn default_handoff_directory() -> String {
341    ".batty/handoff".to_string()
342}
343
344fn default_planning_cycle_cooldown_secs() -> u64 {
345    300
346}
347
348fn default_context_pressure_threshold_bytes() -> u64 {
349    512_000
350}
351
352fn default_context_pressure_restart_delay_secs() -> u64 {
353    120
354}
355
356fn default_sensitive_paths() -> Vec<String> {
357    vec![
358        "Cargo.toml".to_string(),
359        "team.yaml".to_string(),
360        ".env".to_string(),
361    ]
362}
363
364#[derive(Debug, Clone, Deserialize)]
365pub struct AutoMergePolicy {
366    #[serde(default)]
367    pub enabled: bool,
368    #[serde(default = "default_max_diff_lines")]
369    pub max_diff_lines: usize,
370    #[serde(default = "default_max_files_changed")]
371    pub max_files_changed: usize,
372    #[serde(default = "default_max_modules_touched")]
373    pub max_modules_touched: usize,
374    #[serde(default = "default_sensitive_paths")]
375    pub sensitive_paths: Vec<String>,
376    #[serde(default = "default_confidence_threshold")]
377    pub confidence_threshold: f64,
378    #[serde(default = "default_require_tests_pass")]
379    pub require_tests_pass: bool,
380}
381
382fn default_max_diff_lines() -> usize {
383    200
384}
385fn default_max_files_changed() -> usize {
386    5
387}
388fn default_max_modules_touched() -> usize {
389    2
390}
391fn default_narration_detection_threshold() -> usize {
392    6
393}
394fn default_confidence_threshold() -> f64 {
395    0.8
396}
397fn default_require_tests_pass() -> bool {
398    true
399}
400
401impl Default for AutoMergePolicy {
402    fn default() -> Self {
403        Self {
404            enabled: false,
405            max_diff_lines: default_max_diff_lines(),
406            max_files_changed: default_max_files_changed(),
407            max_modules_touched: default_max_modules_touched(),
408            sensitive_paths: default_sensitive_paths(),
409            confidence_threshold: default_confidence_threshold(),
410            require_tests_pass: default_require_tests_pass(),
411        }
412    }
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
416#[serde(rename_all = "snake_case")]
417pub enum WorkflowMode {
418    #[default]
419    Legacy,
420    Hybrid,
421    WorkflowFirst,
422}
423
424#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
425#[serde(rename_all = "snake_case")]
426pub enum OrchestratorPosition {
427    #[default]
428    Left,
429    Bottom,
430}
431
432impl WorkflowMode {
433    #[cfg_attr(not(test), allow(dead_code))]
434    pub fn legacy_runtime_enabled(self) -> bool {
435        matches!(self, Self::Legacy | Self::Hybrid)
436    }
437
438    #[cfg_attr(not(test), allow(dead_code))]
439    pub fn workflow_state_primary(self) -> bool {
440        matches!(self, Self::WorkflowFirst)
441    }
442
443    pub fn enables_runtime_surface(self) -> bool {
444        matches!(self, Self::Hybrid | Self::WorkflowFirst)
445    }
446
447    pub fn as_str(self) -> &'static str {
448        match self {
449            Self::Legacy => "legacy",
450            Self::Hybrid => "hybrid",
451            Self::WorkflowFirst => "workflow_first",
452        }
453    }
454}
455
456#[derive(Debug, Clone, Deserialize)]
457pub struct BoardConfig {
458    #[serde(default = "default_rotation_threshold")]
459    pub rotation_threshold: u32,
460    #[serde(default = "default_board_auto_dispatch")]
461    pub auto_dispatch: bool,
462    #[serde(default = "default_board_auto_replenish")]
463    pub auto_replenish: bool,
464    #[serde(default = "default_state_reconciliation_interval_secs")]
465    pub state_reconciliation_interval_secs: u64,
466    #[serde(default = "default_dispatch_stabilization_delay_secs")]
467    pub dispatch_stabilization_delay_secs: u64,
468    #[serde(default = "default_dispatch_dedup_window_secs")]
469    pub dispatch_dedup_window_secs: u64,
470    #[serde(default = "default_dispatch_manual_cooldown_secs")]
471    pub dispatch_manual_cooldown_secs: u64,
472}
473
474impl Default for BoardConfig {
475    fn default() -> Self {
476        Self {
477            rotation_threshold: default_rotation_threshold(),
478            auto_dispatch: default_board_auto_dispatch(),
479            auto_replenish: default_board_auto_replenish(),
480            state_reconciliation_interval_secs: default_state_reconciliation_interval_secs(),
481            dispatch_stabilization_delay_secs: default_dispatch_stabilization_delay_secs(),
482            dispatch_dedup_window_secs: default_dispatch_dedup_window_secs(),
483            dispatch_manual_cooldown_secs: default_dispatch_manual_cooldown_secs(),
484        }
485    }
486}
487
488#[derive(Debug, Clone, Deserialize)]
489pub struct StandupConfig {
490    #[serde(default = "default_standup_interval")]
491    pub interval_secs: u64,
492    #[serde(default = "default_output_lines")]
493    pub output_lines: u32,
494}
495
496impl Default for StandupConfig {
497    fn default() -> Self {
498        Self {
499            interval_secs: default_standup_interval(),
500            output_lines: default_output_lines(),
501        }
502    }
503}
504
505#[derive(Debug, Clone, Deserialize)]
506pub struct AutomationConfig {
507    #[serde(default = "default_enabled")]
508    pub timeout_nudges: bool,
509    #[serde(default = "default_enabled")]
510    pub standups: bool,
511    #[serde(default)]
512    pub clean_room_mode: bool,
513    #[serde(default = "default_enabled")]
514    pub failure_pattern_detection: bool,
515    #[serde(default = "default_enabled")]
516    pub triage_interventions: bool,
517    #[serde(default = "default_enabled")]
518    pub review_interventions: bool,
519    #[serde(default = "default_enabled")]
520    pub owned_task_interventions: bool,
521    #[serde(default = "default_enabled")]
522    pub manager_dispatch_interventions: bool,
523    #[serde(default = "default_enabled")]
524    pub architect_utilization_interventions: bool,
525    #[serde(default)]
526    pub replenishment_threshold: Option<usize>,
527    #[serde(default = "default_intervention_idle_grace_secs")]
528    pub intervention_idle_grace_secs: u64,
529    #[serde(default = "default_intervention_cooldown_secs")]
530    pub intervention_cooldown_secs: u64,
531    #[serde(default = "default_utilization_recovery_interval_secs")]
532    pub utilization_recovery_interval_secs: u64,
533    #[serde(default = "default_enabled")]
534    pub commit_before_reset: bool,
535}
536
537impl Default for AutomationConfig {
538    fn default() -> Self {
539        Self {
540            timeout_nudges: default_enabled(),
541            standups: default_enabled(),
542            clean_room_mode: false,
543            failure_pattern_detection: default_enabled(),
544            triage_interventions: default_enabled(),
545            review_interventions: default_enabled(),
546            owned_task_interventions: default_enabled(),
547            manager_dispatch_interventions: default_enabled(),
548            architect_utilization_interventions: default_enabled(),
549            replenishment_threshold: None,
550            intervention_idle_grace_secs: default_intervention_idle_grace_secs(),
551            intervention_cooldown_secs: default_intervention_cooldown_secs(),
552            utilization_recovery_interval_secs: default_utilization_recovery_interval_secs(),
553            commit_before_reset: default_enabled(),
554        }
555    }
556}
557
558#[derive(Debug, Clone, Deserialize)]
559pub struct LayoutConfig {
560    pub zones: Vec<ZoneDef>,
561}
562
563#[derive(Debug, Clone, Deserialize)]
564pub struct ZoneDef {
565    pub name: String,
566    pub width_pct: u32,
567    #[serde(default)]
568    pub split: Option<SplitDef>,
569}
570
571#[derive(Debug, Clone, Deserialize)]
572pub struct SplitDef {
573    pub horizontal: u32,
574}
575
576#[derive(Debug, Clone, Deserialize)]
577pub struct RoleDef {
578    pub name: String,
579    pub role_type: RoleType,
580    #[serde(default)]
581    pub agent: Option<String>,
582    #[serde(default = "default_instances")]
583    pub instances: u32,
584    #[serde(default)]
585    pub prompt: Option<String>,
586    #[serde(default)]
587    pub talks_to: Vec<String>,
588    #[serde(default)]
589    pub channel: Option<String>,
590    #[serde(default)]
591    pub channel_config: Option<ChannelConfig>,
592    #[serde(default)]
593    pub nudge_interval_secs: Option<u64>,
594    #[serde(default)]
595    pub receives_standup: Option<bool>,
596    #[serde(default)]
597    pub standup_interval_secs: Option<u64>,
598    #[serde(default)]
599    #[allow(dead_code)] // Parsed for future ownership semantics but not yet enforced.
600    pub owns: Vec<String>,
601    #[serde(default)]
602    pub barrier_group: Option<String>,
603    #[serde(default)]
604    pub use_worktrees: bool,
605}
606
607#[derive(Debug, Clone, Deserialize)]
608pub struct ChannelConfig {
609    pub target: String,
610    pub provider: String,
611    /// Telegram bot token for native API (optional; falls back to provider CLI).
612    /// Can also be set via `BATTY_TELEGRAM_BOT_TOKEN` env var.
613    #[serde(default)]
614    pub bot_token: Option<String>,
615    /// Telegram user IDs allowed to send messages (access control).
616    #[serde(default)]
617    pub allowed_user_ids: Vec<i64>,
618}
619
620#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
621#[serde(rename_all = "lowercase")]
622pub enum RoleType {
623    User,
624    Architect,
625    Manager,
626    Engineer,
627}
628
629// --- Default value functions ---
630
631fn default_rotation_threshold() -> u32 {
632    20
633}
634
635fn default_workflow_mode() -> WorkflowMode {
636    WorkflowMode::Legacy
637}
638
639fn default_board_auto_dispatch() -> bool {
640    true
641}
642
643fn default_board_auto_replenish() -> bool {
644    true
645}
646
647fn default_state_reconciliation_interval_secs() -> u64 {
648    30
649}
650
651fn default_dispatch_stabilization_delay_secs() -> u64 {
652    30
653}
654
655fn default_dispatch_dedup_window_secs() -> u64 {
656    60
657}
658
659fn default_dispatch_manual_cooldown_secs() -> u64 {
660    30
661}
662
663fn default_standup_interval() -> u64 {
664    300
665}
666
667fn default_pipeline_starvation_threshold() -> Option<usize> {
668    Some(1)
669}
670
671fn default_output_lines() -> u32 {
672    30
673}
674
675fn default_instances() -> u32 {
676    1
677}
678
679fn default_escalation_threshold_secs() -> u64 {
680    3600
681}
682
683fn default_review_nudge_threshold_secs() -> u64 {
684    1800
685}
686
687fn default_review_timeout_secs() -> u64 {
688    7200
689}
690
691fn default_stall_threshold_secs() -> u64 {
692    300
693}
694
695fn default_max_stall_restarts() -> u32 {
696    2
697}
698
699fn default_health_check_interval_secs() -> u64 {
700    60
701}
702
703fn default_uncommitted_warn_threshold() -> usize {
704    200
705}
706
707fn default_enabled() -> bool {
708    true
709}
710
711fn default_orchestrator_pane() -> bool {
712    true
713}
714
715fn default_intervention_idle_grace_secs() -> u64 {
716    60
717}
718
719fn default_intervention_cooldown_secs() -> u64 {
720    120
721}
722
723fn default_utilization_recovery_interval_secs() -> u64 {
724    1200
725}
726
727fn default_event_log_max_bytes() -> u64 {
728    DEFAULT_EVENT_LOG_MAX_BYTES
729}
730
731fn default_retro_min_duration_secs() -> u64 {
732    60
733}
734
735fn default_shim_health_check_interval_secs() -> u64 {
736    60
737}
738
739fn default_shim_health_timeout_secs() -> u64 {
740    120
741}
742
743fn default_shim_shutdown_timeout_secs() -> u32 {
744    30
745}
746
747fn default_shim_working_state_timeout_secs() -> u64 {
748    600 // 10 minutes
749}
750
751fn default_pending_queue_max_age_secs() -> u64 {
752    600 // 10 minutes
753}