1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// Copyright © 2025 lituus-io <spicyzhug@gmail.com>
// All Rights Reserved.
// Licensed under PolyForm Noncommercial 1.0.0
//! Skills — Persistent Prompt Context.
//!
//! A [`Skill`] is a reusable set of instructions that gets injected into LLM prompts.
//! Instead of hardcoding domain rules in every prompt string, you build a `Skill`
//! once and attach it to any `reason()` call.
//!
//! # Example
//!
//! ```
//! use kkachi::recursive::skill::Skill;
//!
//! let skill = Skill::new()
//! .instruct("deletionProtection",
//! "Always set deletionProtection: false on all resources.")
//! .instruct("naming",
//! "Use snake_case for all resource names.");
//!
//! assert_eq!(skill.len(), 2);
//! let rendered = skill.render();
//! assert!(rendered.contains("deletionProtection"));
//! assert!(rendered.contains("snake_case"));
//! ```
use smallvec::SmallVec;
use std::borrow::Cow;
/// A reusable instruction that gets injected into LLM prompts.
///
/// Skills provide persistent context — domain rules, coding conventions,
/// or any instruction the LLM should follow across all prompts.
pub struct Skill<'a> {
entries: SmallVec<[SkillEntry<'a>; 4]>,
}
struct SkillEntry<'a> {
label: Cow<'a, str>,
instruction: Cow<'a, str>,
priority: u8,
}
impl<'a> Skill<'a> {
/// Create a new empty skill.
pub fn new() -> Self {
Self {
entries: SmallVec::new(),
}
}
/// Add an instruction with default priority (128).
pub fn instruct(self, label: &'a str, instruction: &'a str) -> Self {
self.instruct_at(label, instruction, 128)
}
/// Add an instruction with explicit priority (lower = earlier in prompt).
pub fn instruct_at(mut self, label: &'a str, instruction: &'a str, priority: u8) -> Self {
self.entries.push(SkillEntry {
label: Cow::Borrowed(label),
instruction: Cow::Borrowed(instruction),
priority,
});
self
}
/// Add an instruction from owned strings with default priority (128).
pub fn instruct_owned(self, label: String, instruction: String) -> Self {
self.instruct_owned_at(label, instruction, 128)
}
/// Add an instruction from owned strings with explicit priority.
pub fn instruct_owned_at(mut self, label: String, instruction: String, priority: u8) -> Self {
self.entries.push(SkillEntry {
label: Cow::Owned(label),
instruction: Cow::Owned(instruction),
priority,
});
self
}
/// Render all instructions into a prompt section.
///
/// Instructions are sorted by priority (lowest first).
/// Returns an empty string if there are no instructions.
pub fn render(&self) -> String {
if self.entries.is_empty() {
return String::new();
}
// Sort indices by priority
let mut indices: SmallVec<[usize; 4]> = (0..self.entries.len()).collect();
indices.sort_by_key(|&i| self.entries[i].priority);
let mut out = String::with_capacity(128);
out.push_str("## Instructions\n");
for i in indices {
let entry = &self.entries[i];
out.push_str("- **");
out.push_str(&entry.label);
out.push_str("**: ");
out.push_str(&entry.instruction);
out.push('\n');
}
out
}
/// Conditionally add an instruction.
///
/// Only adds the instruction if `cond` is true.
pub fn instruct_if(self, cond: bool, label: &'a str, instruction: &'a str) -> Self {
if cond {
self.instruct(label, instruction)
} else {
self
}
}
/// Merge another skill into this one.
///
/// Instructions from `other` are appended. If `other` has an instruction
/// with the same label as one already in `self`, the one from `other`
/// replaces it (last-writer-wins).
pub fn merge(mut self, other: &Skill<'a>) -> Self {
for other_entry in &other.entries {
// Remove existing entry with same label (if any)
self.entries.retain(|e| e.label != other_entry.label);
self.entries.push(SkillEntry {
label: other_entry.label.clone(),
instruction: other_entry.instruction.clone(),
priority: other_entry.priority,
});
}
self
}
/// Number of instructions.
pub fn len(&self) -> usize {
self.entries.len()
}
/// Check if there are no instructions.
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl<'a> Default for Skill<'a> {
fn default() -> Self {
Self::new()
}
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_skill_empty() {
let skill = Skill::new();
assert!(skill.is_empty());
assert_eq!(skill.len(), 0);
assert_eq!(skill.render(), "");
}
#[test]
fn test_skill_single_instruction() {
let skill = Skill::new().instruct("naming", "Use snake_case for all names.");
assert_eq!(skill.len(), 1);
assert!(!skill.is_empty());
let rendered = skill.render();
assert!(rendered.starts_with("## Instructions\n"));
assert!(rendered.contains("**naming**"));
assert!(rendered.contains("Use snake_case"));
}
#[test]
fn test_skill_multiple_sorted() {
let skill = Skill::new()
.instruct_at("low_priority", "This comes last.", 200)
.instruct_at("high_priority", "This comes first.", 10)
.instruct_at("medium_priority", "This comes second.", 100);
assert_eq!(skill.len(), 3);
let rendered = skill.render();
let high_pos = rendered.find("high_priority").unwrap();
let medium_pos = rendered.find("medium_priority").unwrap();
let low_pos = rendered.find("low_priority").unwrap();
assert!(high_pos < medium_pos, "high should come before medium");
assert!(medium_pos < low_pos, "medium should come before low");
}
#[test]
fn test_skill_cow_borrowed() {
let label = "test_label";
let instruction = "test_instruction";
let skill = Skill::new().instruct(label, instruction);
// Verify the entry uses borrowed Cow (no heap allocation for the string itself)
assert_eq!(skill.entries[0].label, Cow::Borrowed("test_label"));
assert_eq!(
skill.entries[0].instruction,
Cow::Borrowed("test_instruction")
);
}
#[test]
fn test_skill_len() {
let skill = Skill::new()
.instruct("a", "instruction a")
.instruct("b", "instruction b")
.instruct("c", "instruction c");
assert_eq!(skill.len(), 3);
}
#[test]
fn test_skill_owned() {
let skill =
Skill::new().instruct_owned("owned_label".to_string(), "owned_instruction".to_string());
assert_eq!(skill.len(), 1);
let rendered = skill.render();
assert!(rendered.contains("owned_label"));
assert!(rendered.contains("owned_instruction"));
}
#[test]
fn test_skill_default_priority() {
// All default priority (128) — should preserve insertion order
let skill = Skill::new()
.instruct("first", "A")
.instruct("second", "B")
.instruct("third", "C");
let rendered = skill.render();
let first_pos = rendered.find("first").unwrap();
let second_pos = rendered.find("second").unwrap();
let third_pos = rendered.find("third").unwrap();
assert!(first_pos < second_pos);
assert!(second_pos < third_pos);
}
}