Skip to content

Commit 5982954

Browse files
committed
feat: add multistep automation
1 parent 5423cb4 commit 5982954

17 files changed

Lines changed: 2926 additions & 120 deletions

File tree

src/agent/agent.rs

Lines changed: 418 additions & 41 deletions
Large diffs are not rendered by default.

src/agent/lesson.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,23 +76,25 @@ pub fn extract_lessons(outcomes: &[ToolOutcome], user_message: &str) -> Vec<Less
7676

7777
// Deduplicate lessons by (tool_name, error_summary prefix)
7878
lessons.dedup_by(|a, b| {
79-
a.tool_name == b.tool_name && truncate_error(&a.error_summary, 60) == truncate_error(&b.error_summary, 60)
79+
a.tool_name == b.tool_name
80+
&& truncate_error(&a.error_summary, 60) == truncate_error(&b.error_summary, 60)
8081
});
8182

8283
lessons
8384
}
8485

8586
/// Persist extracted lessons to memory, skipping duplicates.
86-
pub async fn persist_lessons(
87-
memory: &dyn Memory,
88-
lessons: &[Lesson],
89-
) -> usize {
87+
pub async fn persist_lessons(memory: &dyn Memory, lessons: &[Lesson]) -> usize {
9088
let mut stored = 0;
9189
let category = MemoryCategory::Custom(LESSON_CATEGORY.to_string());
9290

9391
for lesson in lessons {
9492
// Check for existing similar lesson to avoid duplicates
95-
let query = format!("{} {}", lesson.tool_name, truncate_error(&lesson.error_summary, 80));
93+
let query = format!(
94+
"{} {}",
95+
lesson.tool_name,
96+
truncate_error(&lesson.error_summary, 80)
97+
);
9698
if let Ok(existing) = memory.recall(&query, 3, None).await {
9799
let dominated = existing.iter().any(|entry| {
98100
entry.category == category
@@ -170,7 +172,10 @@ fn truncate_error(s: &str, max_chars: usize) -> String {
170172
if first_line.chars().count() <= max_chars {
171173
first_line.to_string()
172174
} else {
173-
let truncated: String = first_line.chars().take(max_chars.saturating_sub(3)).collect();
175+
let truncated: String = first_line
176+
.chars()
177+
.take(max_chars.saturating_sub(3))
178+
.collect();
174179
format!("{truncated}...")
175180
}
176181
}
@@ -222,10 +227,9 @@ fn value_preview(val: &serde_json::Value, max_len: usize) -> String {
222227
fn extract_task_keywords(user_message: &str) -> String {
223228
// Extract meaningful words (>3 chars, lowercase, deduplicated)
224229
let stopwords = [
225-
"the", "and", "for", "that", "this", "with", "from", "have", "will",
226-
"what", "when", "where", "which", "your", "about", "been", "could",
227-
"would", "should", "their", "there", "these", "those", "than",
228-
"them", "then", "they", "were", "also", "into", "just", "some",
230+
"the", "and", "for", "that", "this", "with", "from", "have", "will", "what", "when",
231+
"where", "which", "your", "about", "been", "could", "would", "should", "their", "there",
232+
"these", "those", "than", "them", "then", "they", "were", "also", "into", "just", "some",
229233
"very", "make", "like", "please", "want", "need", "can",
230234
];
231235

@@ -277,14 +281,12 @@ mod tests {
277281

278282
#[test]
279283
fn extract_lessons_no_lesson_when_all_succeed() {
280-
let outcomes = vec![
281-
ToolOutcome {
282-
tool_name: "shell".into(),
283-
arguments: serde_json::json!({"command": "ls"}),
284-
success: true,
285-
output: "file1 file2".into(),
286-
},
287-
];
284+
let outcomes = vec![ToolOutcome {
285+
tool_name: "shell".into(),
286+
arguments: serde_json::json!({"command": "ls"}),
287+
success: true,
288+
output: "file1 file2".into(),
289+
}];
288290

289291
let lessons = extract_lessons(&outcomes, "list files");
290292
assert!(lessons.is_empty());

0 commit comments

Comments
 (0)