llama-cpp-v3-agent-sdk 0.1.7

Agentic tool-use loop on top of llama-cpp-v3 — local LLM agents with built-in tools
Documentation
//! AGENTS.md discovery and loading.
//!
//! The agent automatically discovers and loads `AGENTS.md` files for
//! project-specific guidance. Files are searched from the current working
//! directory upward to the git root, plus a global `~/.llama-agent/AGENTS.md`.
//!
//! In monorepos, nested `AGENTS.md` files are all loaded — the most local
//! file takes highest precedence.

use std::path::{Path, PathBuf};

/// A discovered AGENTS.md file with its content and source path.
#[derive(Debug, Clone)]
pub struct AgentsMdFile {
    /// Absolute path to the AGENTS.md file.
    pub path: PathBuf,
    /// Full content of the file.
    pub content: String,
}

/// Discovers and loads AGENTS.md files.
pub struct AgentsMdRegistry {
    files: Vec<AgentsMdFile>,
}

impl AgentsMdRegistry {
    pub fn new() -> Self {
        Self { files: Vec::new() }
    }

    /// Discover AGENTS.md files from CWD up to the git root, plus global.
    pub fn discover(&mut self) {
        self.files.clear();

        // 1. Walk from CWD upward to git root
        if let Ok(cwd) = std::env::current_dir() {
            let git_root = find_git_root(&cwd);
            let stop_at = git_root.as_deref();

            let mut dir = Some(cwd.as_path());
            while let Some(current) = dir {
                let agents_path = current.join("AGENTS.md");
                if agents_path.is_file() {
                    if let Ok(content) = std::fs::read_to_string(&agents_path) {
                        self.files.push(AgentsMdFile {
                            path: agents_path,
                            content,
                        });
                    }
                }

                // Stop at git root
                if let Some(root) = stop_at {
                    if current == root {
                        break;
                    }
                }

                dir = current.parent();
            }
        }

        // 2. Global ~/.llama-agent/AGENTS.md
        if let Some(home) = dirs::home_dir() {
            let global_path = home.join(".llama-agent").join("AGENTS.md");
            if global_path.is_file() {
                // Don't add if already found (dedup by path)
                if !self.files.iter().any(|f| f.path == global_path) {
                    if let Ok(content) = std::fs::read_to_string(&global_path) {
                        self.files.push(AgentsMdFile {
                            path: global_path,
                            content,
                        });
                    }
                }
            }
        }
    }

    /// Get all discovered AGENTS.md files (ordered by precedence: most local first).
    pub fn files(&self) -> &[AgentsMdFile] {
        &self.files
    }

    /// Generate a system prompt fragment with all AGENTS.md content.
    ///
    /// Files are included in order of precedence (most local first).
    pub fn agents_md_prompt(&self) -> String {
        if self.files.is_empty() {
            return String::new();
        }

        let mut lines = Vec::new();
        lines.push("# Project Guidelines (from AGENTS.md)\n".to_string());

        for file in &self.files {
            let path_display = file.path.display().to_string();
            lines.push(format!("<!-- Source: {} -->\n", path_display));
            lines.push(file.content.clone());
            lines.push(String::new());
        }

        lines.join("\n")
    }

    /// Total number of discovered files.
    pub fn len(&self) -> usize {
        self.files.len()
    }

    pub fn is_empty(&self) -> bool {
        self.files.is_empty()
    }
}

impl Default for AgentsMdRegistry {
    fn default() -> Self {
        Self::new()
    }
}

/// Walk upward from `start` to find a `.git` directory.
fn find_git_root(start: &Path) -> Option<PathBuf> {
    let mut dir = Some(start);
    while let Some(current) = dir {
        if current.join(".git").exists() {
            return Some(current.to_path_buf());
        }
        dir = current.parent();
    }
    None
}