Skip to main content

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}