codetether_agent/session/delegation_skills.rs
1//! Canonical skill-name constants for CADMAS-CTX delegation hooks.
2//!
3//! ## Why a dedicated module
4//!
5//! Each routing surface in codetether — provider failover, swarm
6//! dispatch, ralph handoff, autochat persona relay, RLM compaction
7//! model — wants to key its per-(agent, skill, bucket) Beta
8//! posteriors against a stable `skill` string. Spelling drift across
9//! call sites ("model_call" vs "router" vs "provider_call") would
10//! silently fragment posteriors and starve the LCB score of
11//! evidence.
12//!
13//! This module centralises the catalogue. Every site that calls
14//! [`DelegationState::update`] or [`DelegationState::score`] **must**
15//! pass one of these constants as the `skill` argument.
16//!
17//! ## Stable shape
18//!
19//! * Names are lowercase `snake_case`.
20//! * Never renamed — adding a new surface gets a new constant; old
21//! ones stay so existing sidecar data remains readable.
22//! * Documented alongside the codetether surface they key.
23//!
24//! [`DelegationState::update`]: crate::session::delegation::DelegationState::update
25//! [`DelegationState::score`]: crate::session::delegation::DelegationState::score
26//!
27//! ## Examples
28//!
29//! ```rust
30//! use codetether_agent::session::delegation_skills;
31//!
32//! assert_eq!(delegation_skills::MODEL_CALL, "model_call");
33//! assert!(delegation_skills::ALL.contains(&"swarm_dispatch"));
34//! ```
35
36/// Skill for provider-failover routing in
37/// [`choose_router_target_bandit`](crate::session::helper::router::choose_router_target_bandit).
38pub const MODEL_CALL: &str = "model_call";
39
40/// Skill for RLM compaction model selection in
41/// [`resolve_rlm_model_bandit`](crate::session::helper::compression::resolve_rlm_model_bandit).
42pub const RLM_COMPACT: &str = "rlm_compact";
43
44/// Skill for swarm-executor dispatch
45/// (`src/swarm/orchestrator.rs` — Phase C step 28).
46pub const SWARM_DISPATCH: &str = "swarm_dispatch";
47
48/// Skill for ralph-loop persona handoff
49/// (`src/ralph/ralph_loop.rs` — Phase C step 29).
50pub const RALPH_HANDOFF: &str = "ralph_handoff";
51
52/// Skill for TUI autochat next-persona selection
53/// (`src/tui/app/autochat/` — Phase C step 31).
54pub const AUTOCHAT_PERSONA: &str = "autochat_persona";
55
56/// Complete list of registered skill names.
57///
58/// New surfaces go here *and* get their own constant so grep-ability
59/// across the codebase survives refactors.
60pub const ALL: &[&str] = &[
61 MODEL_CALL,
62 RLM_COMPACT,
63 SWARM_DISPATCH,
64 RALPH_HANDOFF,
65 AUTOCHAT_PERSONA,
66];
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn all_skills_are_unique_and_non_empty() {
74 let mut copy: Vec<&str> = ALL.to_vec();
75 copy.sort_unstable();
76 copy.dedup();
77 assert_eq!(copy.len(), ALL.len(), "skill names must be unique");
78 for name in ALL {
79 assert!(!name.is_empty());
80 assert_eq!(
81 name.to_ascii_lowercase(),
82 *name,
83 "skills must be snake_case"
84 );
85 }
86 }
87
88 #[test]
89 fn model_call_is_stable() {
90 // Changing this string would silently fragment existing
91 // DelegationState sidecars. Unit-test it so the rename is
92 // caught in review, not in production.
93 assert_eq!(MODEL_CALL, "model_call");
94 }
95}