From b555256d214c8a88e60bbf19784471502ed5fb82 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Fri, 26 Dec 2025 18:35:46 +0100 Subject: [PATCH] docs(agent): Add doc-comments to all public items in agent-core --- crates/core/agent/src/compact.rs | 31 ++++++----- crates/core/agent/src/git.rs | 69 ++++++++++------------- crates/core/agent/src/lib.rs | 49 ++++++++++++----- crates/core/agent/src/session.rs | 76 ++++++++++++++++++++------ crates/core/agent/src/system_prompt.rs | 32 ++++++----- 5 files changed, 158 insertions(+), 99 deletions(-) diff --git a/crates/core/agent/src/compact.rs b/crates/core/agent/src/compact.rs index f5c7617..1f083ae 100644 --- a/crates/core/agent/src/compact.rs +++ b/crates/core/agent/src/compact.rs @@ -1,4 +1,4 @@ -//! Context compaction for long conversations +//! Context compaction for long conversations. //! //! When the conversation context grows too large, this module compacts //! earlier messages into a summary while preserving recent context. @@ -6,16 +6,16 @@ use color_eyre::eyre::Result; use llm_core::{ChatMessage, ChatOptions, LlmProvider}; -/// Token limit threshold for triggering compaction +/// Token limit threshold for triggering compaction. const CONTEXT_LIMIT: usize = 180_000; -/// Threshold ratio at which to trigger compaction (90% of limit) +/// Threshold ratio at which to trigger compaction (90% of limit). const COMPACTION_THRESHOLD: f64 = 0.9; -/// Number of recent messages to preserve during compaction +/// Number of recent messages to preserve during compaction. const PRESERVE_RECENT: usize = 10; -/// Token counter for estimating context size +/// Token counter for estimating the size of conversation history in tokens. pub struct TokenCounter { chars_per_token: f64, } @@ -27,12 +27,13 @@ impl Default for TokenCounter { } impl TokenCounter { + /// Creates a new `TokenCounter` with default settings. pub fn new() -> Self { // Rough estimate: ~4 chars per token for English text Self { chars_per_token: 4.0 } } - /// Estimate token count for a message + /// Estimates the token count for a single message. pub fn count_message(&self, message: &ChatMessage) -> usize { let content_len = message.content.as_ref().map(|c| c.len()).unwrap_or(0); // Add overhead for role, metadata @@ -40,19 +41,19 @@ impl TokenCounter { ((content_len as f64 / self.chars_per_token) as usize) + overhead } - /// Estimate total token count for all messages + /// Estimates the total token count for a list of messages. pub fn count_messages(&self, messages: &[ChatMessage]) -> usize { messages.iter().map(|m| self.count_message(m)).sum() } - /// Check if context should be compacted + /// Determines if the conversation history should be compacted based on token limits. pub fn should_compact(&self, messages: &[ChatMessage]) -> bool { let count = self.count_messages(messages); count > (CONTEXT_LIMIT as f64 * COMPACTION_THRESHOLD) as usize } } -/// Context compactor that summarizes conversation history +/// Context compactor that uses an LLM to summarize conversation history. pub struct Compactor { token_counter: TokenCounter, } @@ -64,22 +65,22 @@ impl Default for Compactor { } impl Compactor { + /// Creates a new `Compactor`. pub fn new() -> Self { Self { token_counter: TokenCounter::new(), } } - /// Check if messages need compaction + /// Returns `true` if the provided messages exceed the compaction threshold. pub fn needs_compaction(&self, messages: &[ChatMessage]) -> bool { self.token_counter.should_compact(messages) } - /// Compact messages by summarizing earlier conversation + /// Compacts conversation history by summarizing earlier messages. /// - /// Returns compacted messages with: - /// - A system message containing the summary of earlier context - /// - The most recent N messages preserved in full + /// The resulting message list will contain a summary system message followed + /// by the most recent conversation context. pub async fn compact( &self, provider: &P, @@ -169,7 +170,7 @@ impl Compactor { Ok(summary.trim().to_string()) } - /// Get token counter for external use + /// Returns a reference to the internal `TokenCounter`. pub fn token_counter(&self) -> &TokenCounter { &self.token_counter } diff --git a/crates/core/agent/src/git.rs b/crates/core/agent/src/git.rs index 9916aef..5c5e5ba 100644 --- a/crates/core/agent/src/git.rs +++ b/crates/core/agent/src/git.rs @@ -9,23 +9,25 @@ use color_eyre::eyre::Result; use std::path::Path; use std::process::Command; -/// Status of a file in the git working tree +/// Represents the git status of a specific file. #[derive(Debug, Clone, PartialEq, Eq)] pub enum GitFileStatus { - /// File has been modified + /// File has been modified. Modified { path: String }, - /// File has been added (staged) + /// File has been added to the index (staged). Added { path: String }, - /// File has been deleted + /// File has been deleted. Deleted { path: String }, - /// File has been renamed + /// File has been renamed. Renamed { from: String, to: String }, - /// File is untracked + /// File is not tracked by git. Untracked { path: String }, } impl GitFileStatus { - /// Get the primary path associated with this status + /// Returns the primary path associated with this status entry. + /// + /// For renames, this returns the new path. pub fn path(&self) -> &str { match self { Self::Modified { path } => path, @@ -37,25 +39,25 @@ impl GitFileStatus { } } -/// Complete state of a git repository +/// Represents the captured state of a git repository. #[derive(Debug, Clone)] pub struct GitState { - /// Whether the current directory is in a git repository + /// `true` if the directory is a git repository. pub is_git_repo: bool, - /// Current branch name (None if not in a repo or detached HEAD) + /// The name of the current branch, if any. pub current_branch: Option, - /// Main branch name (main/master, None if not detected) + /// The name of the detected main/master branch. pub main_branch: Option, - /// Status of files in the working tree + /// List of file status entries for the working tree. pub status: Vec, - /// Whether there are any uncommitted changes + /// `true` if there are any staged or unstaged changes. pub has_uncommitted_changes: bool, - /// Remote URL for the repository (None if no remote configured) + /// The URL of the 'origin' remote, if configured. pub remote_url: Option, } impl GitState { - /// Create a default GitState for non-git directories + /// Creates a `GitState` representing a non-repository directory. pub fn not_a_repo() -> Self { Self { is_git_repo: false, @@ -68,10 +70,11 @@ impl GitState { } } -/// Detect the current git repository state +/// Detects the git state of the specified working directory. /// -/// This function runs various git commands to gather information about the repository. -/// If git is not available or the directory is not a git repo, returns a default state. +/// This function executes git commands to inspect the repository. It handles cases +/// where git is missing or the directory is not a repository by returning a +/// "not a repo" state rather than an error. pub fn detect_git_state(working_dir: &Path) -> Result { // Check if this is a git repository let is_repo = Command::new("git") @@ -252,13 +255,10 @@ fn get_remote_url(working_dir: &Path) -> Result> { } } -/// Check if a git command is safe (read-only) +/// Heuristically determines if a git command string is "safe" (read-only). /// -/// Safe commands include: -/// - status, log, show, diff, branch (without -D) -/// - remote (without add/remove) -/// - config --get -/// - rev-parse, ls-files, ls-tree +/// Safe commands (like `status` or `diff`) are generally allowed without +/// explicit confirmation in certain modes. pub fn is_safe_git_command(command: &str) -> bool { let parts: Vec<&str> = command.split_whitespace().collect(); @@ -287,13 +287,11 @@ pub fn is_safe_git_command(command: &str) -> bool { } } -/// Check if a git command is destructive +/// Heuristically determines if a git command is "destructive" or potentially dangerous. /// -/// Returns (is_destructive, warning_message) tuple. -/// Destructive commands include: -/// - push --force, reset --hard, clean -fd -/// - rebase, amend, filter-branch -/// - branch -D, tag -d +/// Returns a tuple of `(is_destructive, warning_message)`. Destructive commands +/// (like `reset --hard` or `push --force`) should always trigger a warning or +/// require explicit approval. pub fn is_destructive_git_command(command: &str) -> (bool, &'static str) { let cmd_lower = command.to_lowercase(); @@ -347,16 +345,7 @@ pub fn is_destructive_git_command(command: &str) -> (bool, &'static str) { (false, "") } -/// Format git state for human-readable display -/// -/// Example output: -/// ```text -/// Git Repository: yes -/// Current branch: feature-branch -/// Main branch: main -/// Status: 3 modified, 1 untracked -/// Remote: https://github.com/user/repo.git -/// ``` +/// Formats a `GitState` into a human-readable summary string. pub fn format_git_status(state: &GitState) -> String { if !state.is_git_repo { return "Not a git repository".to_string(); diff --git a/crates/core/agent/src/lib.rs b/crates/core/agent/src/lib.rs index 2e9840b..a2d9ce4 100644 --- a/crates/core/agent/src/lib.rs +++ b/crates/core/agent/src/lib.rs @@ -1,3 +1,8 @@ +//! Core agent orchestration for the Owlen AI agent. +//! +//! This crate provides the central engine that coordinates Large Language Models (LLMs), +//! a rich set of system tools, and user interaction through an event-driven loop. + pub mod session; pub mod system_prompt; pub mod git; @@ -71,27 +76,27 @@ pub enum AgentEvent { pub type AgentEventSender = mpsc::Sender; pub type AgentEventReceiver = mpsc::Receiver; -/// Create channel for agent events +/// Creates a channel for agent events with a buffer size of 100. pub fn create_event_channel() -> (AgentEventSender, AgentEventReceiver) { mpsc::channel(100) } -/// Optional context for tools that need external dependencies +/// Optional context for tools that need external dependencies or shared state. #[derive(Clone)] pub struct ToolContext { - /// Todo list for TodoWrite tool + /// Todo list for the `todo_write` tool. pub todo_list: Option, - /// Channel for asking user questions + /// Channel for asking user questions via the `ask_user` tool. pub ask_sender: Option, - /// Shell manager for background shells + /// Shell manager for handling background shells. pub shell_manager: Option, - /// Plan manager for planning mode + /// Plan manager for managing implementation plans in planning mode. pub plan_manager: Option>, - /// Current agent mode (normal or planning) + /// Current agent mode (e.g., Normal or Planning). pub agent_mode: Arc>, } @@ -108,52 +113,58 @@ impl Default for ToolContext { } impl ToolContext { + /// Creates a new, empty `ToolContext`. pub fn new() -> Self { Self::default() } + /// Adds a `TodoList` to the context. pub fn with_todo_list(mut self, list: TodoList) -> Self { self.todo_list = Some(list); self } + /// Adds an `AskSender` to the context for user interaction. pub fn with_ask_sender(mut self, sender: AskSender) -> Self { self.ask_sender = Some(sender); self } + /// Adds a `ShellManager` to the context for managing bash sessions. pub fn with_shell_manager(mut self, manager: ShellManager) -> Self { self.shell_manager = Some(manager); self } + /// Adds a `PlanManager` to the context. pub fn with_plan_manager(mut self, manager: PlanManager) -> Self { self.plan_manager = Some(Arc::new(manager)); self } + /// Initializes a `PlanManager` with the given project root and adds it to the context. pub fn with_project_root(mut self, project_root: PathBuf) -> Self { self.plan_manager = Some(Arc::new(PlanManager::new(project_root))); self } - /// Check if agent is in planning mode + /// Checks if the agent is currently in planning mode. pub async fn is_planning(&self) -> bool { self.agent_mode.read().await.is_planning() } - /// Get current agent mode + /// Returns the current `AgentMode`. pub async fn get_mode(&self) -> AgentMode { self.agent_mode.read().await.clone() } - /// Set agent mode + /// Sets the current `AgentMode`. pub async fn set_mode(&self, mode: AgentMode) { *self.agent_mode.write().await = mode; } } -/// Define all available tools for the LLM +/// Returns definitions for all available tools that the agent can use. pub fn get_tool_definitions() -> Vec { vec![ Tool::function( @@ -466,7 +477,10 @@ impl ToolCallsBuilder { } } -/// Execute a tool call and return the result +/// Executes a single tool call and returns its result as a string. +/// +/// This function handles permission checking and interacts with various tool-specific +/// backends (filesystem, shell, etc.) using the provided `ToolContext`. pub async fn execute_tool( tool_name: &str, arguments: &Value, @@ -858,7 +872,11 @@ pub async fn execute_tool( } } -/// Run the agent loop with tool calling +/// Runs the core agent loop for a given user prompt. +/// +/// This function iteratively calls the LLM provider, processes tool execution requests, +/// and feeds the results back to the model until a final text response is generated +/// or the iteration limit is reached. pub async fn run_agent_loop( provider: &P, user_prompt: &str, @@ -988,7 +1006,10 @@ pub async fn run_agent_loop( } } -/// Run agent loop with event streaming +/// Runs the agent loop and streams events back through the provided channel. +/// +/// This allows UIs to provide real-time feedback as the LLM generates text and +/// as tools are being executed. pub async fn run_agent_loop_streaming( provider: &P, user_prompt: &str, diff --git a/crates/core/agent/src/session.rs b/crates/core/agent/src/session.rs index 6ba5f17..e28fd64 100644 --- a/crates/core/agent/src/session.rs +++ b/crates/core/agent/src/session.rs @@ -1,3 +1,8 @@ +//! Session state and history management. +//! +//! This module provides tools for tracking conversation history, capturing +//! usage statistics, and managing session checkpoints for persistence and rewind. + use color_eyre::eyre::{Result, eyre}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -5,16 +10,23 @@ use std::fs; use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime}; +/// Statistics for a single chat session. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SessionStats { + /// The time when the session started. pub start_time: SystemTime, + /// Total number of messages exchanged. pub total_messages: usize, + /// Total number of tools executed by the agent. pub total_tool_calls: usize, + /// Total wall-clock time spent in the session. pub total_duration: Duration, + /// Rough estimate of the total tokens used. pub estimated_tokens: usize, } impl SessionStats { + /// Creates a new `SessionStats` instance with zeroed values. pub fn new() -> Self { Self { start_time: SystemTime::now(), @@ -25,16 +37,19 @@ impl SessionStats { } } + /// Records a new message in the statistics. pub fn record_message(&mut self, tokens: usize, duration: Duration) { self.total_messages += 1; self.estimated_tokens += tokens; self.total_duration += duration; } + /// Increments the tool call counter. pub fn record_tool_call(&mut self) { self.total_tool_calls += 1; } + /// Formats a duration into a human-readable string. pub fn format_duration(d: Duration) -> String { let secs = d.as_secs(); if secs < 60 { @@ -53,22 +68,32 @@ impl Default for SessionStats { } } +/// In-memory history of the current session. #[derive(Debug, Clone)] pub struct SessionHistory { + /// List of prompts provided by the user. pub user_prompts: Vec, + /// List of responses generated by the assistant. pub assistant_responses: Vec, + /// Chronological log of all tool calls made. pub tool_calls: Vec, } +/// Record of a single tool execution. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ToolCallRecord { + /// Name of the tool that was called. pub tool_name: String, + /// JSON-encoded arguments provided to the tool. pub arguments: String, + /// Output produced by the tool. pub result: String, + /// Whether the tool execution was successful. pub success: bool, } impl SessionHistory { + /// Creates a new, empty `SessionHistory`. pub fn new() -> Self { Self { user_prompts: Vec::new(), @@ -77,18 +102,22 @@ impl SessionHistory { } } + /// Appends a user message to history. pub fn add_user_message(&mut self, message: String) { self.user_prompts.push(message); } + /// Appends an assistant response to history. pub fn add_assistant_message(&mut self, message: String) { self.assistant_responses.push(message); } + /// Appends a tool call record to history. pub fn add_tool_call(&mut self, record: ToolCallRecord) { self.tool_calls.push(record); } + /// Clears all stored history. pub fn clear(&mut self) { self.user_prompts.clear(); self.assistant_responses.clear(); @@ -102,17 +131,21 @@ impl Default for SessionHistory { } } -/// Represents a file modification with before/after content +/// Represents a file modification with before/after content. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FileDiff { + /// Absolute path to the file. pub path: PathBuf, + /// Content of the file before modification. pub before: String, + /// Content of the file after modification. pub after: String, + /// When the modification occurred. pub timestamp: SystemTime, } impl FileDiff { - /// Create a new file diff + /// Creates a new `FileDiff`. pub fn new(path: PathBuf, before: String, after: String) -> Self { Self { path, @@ -123,20 +156,27 @@ impl FileDiff { } } -/// A checkpoint captures the state of a session at a point in time +/// A checkpoint captures the full state of a session at a point in time. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Checkpoint { + /// Unique identifier for the checkpoint. pub id: String, + /// When the checkpoint was created. pub timestamp: SystemTime, + /// Session statistics at the time of checkpoint. pub stats: SessionStats, + /// History of user prompts. pub user_prompts: Vec, + /// History of assistant responses. pub assistant_responses: Vec, + /// History of tool calls. pub tool_calls: Vec, + /// List of file modifications made during the session. pub file_diffs: Vec, } impl Checkpoint { - /// Create a new checkpoint from current session state + /// Creates a new checkpoint from the current session state. pub fn new( id: String, stats: SessionStats, @@ -154,7 +194,7 @@ impl Checkpoint { } } - /// Save checkpoint to disk + /// Saves the checkpoint to a JSON file on disk. pub fn save(&self, checkpoint_dir: &Path) -> Result<()> { fs::create_dir_all(checkpoint_dir)?; let path = checkpoint_dir.join(format!("{}.json", self.id)); @@ -163,7 +203,7 @@ impl Checkpoint { Ok(()) } - /// Load checkpoint from disk + /// Loads a checkpoint from disk by ID. pub fn load(checkpoint_dir: &Path, id: &str) -> Result { let path = checkpoint_dir.join(format!("{}.json", id)); let content = fs::read_to_string(&path) @@ -173,7 +213,7 @@ impl Checkpoint { Ok(checkpoint) } - /// List all available checkpoints in a directory + /// Lists all available checkpoint IDs in the given directory. pub fn list(checkpoint_dir: &Path) -> Result> { if !checkpoint_dir.exists() { return Ok(Vec::new()); @@ -196,14 +236,14 @@ impl Checkpoint { } } -/// Session checkpoint manager +/// Manages the creation and restoration of session checkpoints. pub struct CheckpointManager { checkpoint_dir: PathBuf, file_snapshots: HashMap, } impl CheckpointManager { - /// Create a new checkpoint manager + /// Creates a new `CheckpointManager` pointing to the specified directory. pub fn new(checkpoint_dir: PathBuf) -> Self { Self { checkpoint_dir, @@ -211,7 +251,7 @@ impl CheckpointManager { } } - /// Snapshot a file's current content before modification + /// Snapshots a file's current content before modification to track changes. pub fn snapshot_file(&mut self, path: &Path) -> Result<()> { if !self.file_snapshots.contains_key(path) { let content = fs::read_to_string(path).unwrap_or_default(); @@ -220,7 +260,7 @@ impl CheckpointManager { Ok(()) } - /// Create a file diff after modification + /// Creates a `FileDiff` if the file has been modified since it was snapshotted. pub fn create_diff(&self, path: &Path) -> Result> { if let Some(before) = self.file_snapshots.get(path) { let after = fs::read_to_string(path).unwrap_or_default(); @@ -238,7 +278,7 @@ impl CheckpointManager { } } - /// Get all file diffs since last checkpoint + /// Returns all file modifications tracked since the last checkpoint. pub fn get_all_diffs(&self) -> Result> { let mut diffs = Vec::new(); for (path, before) in &self.file_snapshots { @@ -250,12 +290,12 @@ impl CheckpointManager { Ok(diffs) } - /// Clear file snapshots + /// Clears all internal file snapshots. pub fn clear_snapshots(&mut self) { self.file_snapshots.clear(); } - /// Save a checkpoint + /// Saves the current session state as a new checkpoint. pub fn save_checkpoint( &mut self, id: String, @@ -269,17 +309,19 @@ impl CheckpointManager { Ok(checkpoint) } - /// Load a checkpoint + /// Loads a checkpoint by ID. pub fn load_checkpoint(&self, id: &str) -> Result { Checkpoint::load(&self.checkpoint_dir, id) } - /// List all checkpoints + /// Lists all available checkpoints. pub fn list_checkpoints(&self) -> Result> { Checkpoint::list(&self.checkpoint_dir) } - /// Rewind to a checkpoint by restoring file contents + /// Rewinds the local filesystem to the state captured in the specified checkpoint. + /// + /// Returns a list of paths that were restored. pub fn rewind_to(&self, checkpoint_id: &str) -> Result> { let checkpoint = self.load_checkpoint(checkpoint_id)?; let mut restored_files = Vec::new(); diff --git a/crates/core/agent/src/system_prompt.rs b/crates/core/agent/src/system_prompt.rs index 1a5934b..2970a0c 100644 --- a/crates/core/agent/src/system_prompt.rs +++ b/crates/core/agent/src/system_prompt.rs @@ -1,10 +1,12 @@ -//! System Prompt Management +//! System Prompt Management. //! -//! Composes system prompts from multiple sources for agent sessions. +//! This module is responsible for composing the complex system prompts sent to the LLM. +//! It merges base instructions, tool definitions, project-specific context, and +//! dynamically injected content from skills and hooks. use std::path::Path; -/// Builder for composing system prompts +/// Builder for incrementally composing a system prompt from various sources. #[derive(Debug, Clone, Default)] pub struct SystemPromptBuilder { sections: Vec, @@ -19,11 +21,12 @@ struct PromptSection { } impl SystemPromptBuilder { + /// Creates a new, empty `SystemPromptBuilder`. pub fn new() -> Self { Self::default() } - /// Add the base agent prompt + /// Adds the base agent identity and instructions section. pub fn with_base_prompt(mut self, content: impl Into) -> Self { self.sections.push(PromptSection { name: "base".to_string(), @@ -33,7 +36,7 @@ impl SystemPromptBuilder { self } - /// Add tool usage instructions + /// Adds tool usage instructions. pub fn with_tool_instructions(mut self, content: impl Into) -> Self { self.sections.push(PromptSection { name: "tools".to_string(), @@ -43,7 +46,8 @@ impl SystemPromptBuilder { self } - /// Load and add project instructions from CLAUDE.md or .owlen.md + /// Attempts to load project-specific instructions from `CLAUDE.md` or `.owlen.md` + /// located in the provided project root. pub fn with_project_instructions(mut self, project_root: &Path) -> Self { // Try CLAUDE.md first (Claude Code compatibility) let claude_md = project_root.join("CLAUDE.md"); @@ -73,7 +77,7 @@ impl SystemPromptBuilder { self } - /// Add skill content + /// Adds domain-specific knowledge or instructions as a "skill". pub fn with_skill(mut self, skill_name: &str, content: impl Into) -> Self { self.sections.push(PromptSection { name: format!("skill:{}", skill_name), @@ -83,7 +87,7 @@ impl SystemPromptBuilder { self } - /// Add hook-injected content (from SessionStart hooks) + /// Injects context dynamically from hooks (e.g., during session initialization). pub fn with_hook_injection(mut self, content: impl Into) -> Self { self.sections.push(PromptSection { name: "hook".to_string(), @@ -93,7 +97,7 @@ impl SystemPromptBuilder { self } - /// Add custom section + /// Adds a generic custom section with a specific name and priority. pub fn with_section(mut self, name: impl Into, content: impl Into, priority: i32) -> Self { self.sections.push(PromptSection { name: name.into(), @@ -103,7 +107,9 @@ impl SystemPromptBuilder { self } - /// Build the final system prompt + /// Finalizes the build process and returns the combined system prompt string. + /// + /// Sections are sorted by priority and separated by a horizontal rule (`---`). pub fn build(mut self) -> String { // Sort by priority self.sections.sort_by_key(|s| s.priority); @@ -116,13 +122,13 @@ impl SystemPromptBuilder { .join("\n\n---\n\n") } - /// Check if any content has been added + /// Returns `true` if no prompt sections have been added yet. pub fn is_empty(&self) -> bool { self.sections.is_empty() } } -/// Default base prompt for Owlen agent +/// Returns the standard default identity and guideline prompt for the Owlen agent. pub fn default_base_prompt() -> &'static str { r#"You are Owlen, an AI assistant that helps with software engineering tasks. @@ -145,7 +151,7 @@ You have access to tools for reading files, writing code, running commands, and - Use `web_search` for current information"# } -/// Generate tool instructions based on available tools +/// Dynamically generates a summary of available tools to be included in the system prompt. pub fn generate_tool_instructions(tool_names: &[&str]) -> String { let mut instructions = String::from("## Available Tools\n\n");