Save learnings to the agent's context file. If the file doesn't exist, creates it with the learnings section. If the file exists but has no learnings section, appends the section. If the file exists with a learnings section, merges new learnings (deduplicating by finding text) and rewrites the section. # Arguments `repo_root` — Path to the repository root `agent_name` — Agent registry key (dete
(
repo_root: &Path,
agent_name: &str,
learnings: &Learnings,
)
| 360 | /// |
| 361 | /// Returns an error if the file cannot be read or written. |
| 362 | pub fn save_learnings_to_context_file( |
| 363 | repo_root: &Path, |
| 364 | agent_name: &str, |
| 365 | learnings: &Learnings, |
| 366 | ) -> Result<SaveResult, std::io::Error> { |
| 367 | if learnings.is_empty() { |
| 368 | return Ok(SaveResult { |
| 369 | file_path: context_file_path(repo_root, agent_name), |
| 370 | added: 0, |
| 371 | skipped_duplicates: 0, |
| 372 | }); |
| 373 | } |
| 374 | |
| 375 | let file_path = context_file_path(repo_root, agent_name); |
| 376 | |
| 377 | // Ensure parent directory exists (for .atomic/learnings.md) |
| 378 | if let Some(parent) = file_path.parent() { |
| 379 | std::fs::create_dir_all(parent)?; |
| 380 | } |
| 381 | |
| 382 | // Read existing content (or empty string if file doesn't exist) |
| 383 | let existing_content = std::fs::read_to_string(&file_path).unwrap_or_default(); |
| 384 | |
| 385 | // Dedup against existing learnings |
| 386 | let original_count = context_file_learning_count(learnings); |
| 387 | let deduped = dedup_learnings(&existing_content, learnings); |
| 388 | let new_count = context_file_learning_count(&deduped); |
| 389 | let skipped = original_count - new_count; |
| 390 | |
| 391 | if new_count == 0 { |
| 392 | return Ok(SaveResult { |
| 393 | file_path, |
| 394 | added: 0, |
| 395 | skipped_duplicates: skipped, |
| 396 | }); |
| 397 | } |
| 398 | |
| 399 | // Format the new learnings as individual items and merge by heading |
| 400 | let new_items = collect_new_items(&deduped); |
| 401 | |
| 402 | // Build the updated file content |
| 403 | let updated_content = if existing_content.contains(SECTION_START) { |
| 404 | // Merge into existing headings (no duplicate ### Repo / ### Workflow) |
| 405 | merge_items_into_section(&existing_content, &new_items) |
| 406 | } else if existing_content.is_empty() { |
| 407 | let new_markdown = format_learnings_markdown(&deduped); |
| 408 | // New file — create with just the learnings section |
| 409 | build_new_section(&new_markdown) |
| 410 | } else { |
| 411 | // Existing file without a learnings section — append |
| 412 | let new_markdown = format_learnings_markdown(&deduped); |
| 413 | let mut content = existing_content.clone(); |
| 414 | if !content.ends_with('\n') { |
| 415 | content.push('\n'); |
| 416 | } |
| 417 | content.push('\n'); |
| 418 | content.push_str(&build_new_section(&new_markdown)); |
| 419 | content |