From 5c59506955e5096ede0a5ecff8c495986cd6a5d4 Mon Sep 17 00:00:00 2001 From: jp Date: Thu, 12 Feb 2026 12:05:22 -0300 Subject: [PATCH 01/14] Rename `/tools` command to `/show-tools` --- CLI.md | 2 +- src/smolagents/bp_cli.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CLI.md b/CLI.md index d0558ce05..1de66641a 100644 --- a/CLI.md +++ b/CLI.md @@ -142,7 +142,7 @@ Use `prompt_toolkit` for: | `/show-step ` | Show full content of a specific step | | `/show-steps` | Show one-line summary of all memory steps | | `/steps ` | Change max_steps for the agent | -| `/tools` | List all loaded tools | +| `/show-tools` | List all loaded tools | | `/undo-steps [N]` | Remove last N steps from memory (default: 1) | | `/verbose` | Toggle verbose output | diff --git a/src/smolagents/bp_cli.py b/src/smolagents/bp_cli.py index 72307129f..659f25869 100644 --- a/src/smolagents/bp_cli.py +++ b/src/smolagents/bp_cli.py @@ -452,7 +452,7 @@ def print_banner(model_id: str, server_model: str, tool_count: int): "/load-instructions", "/plan", "/pwd", "/run", "/save", "/session-load", "/session-save", "/show-compression-stats", "/show-memory-stats", "/show-stats", - "/save-step", "/show-step", "/show-steps", "/steps", "/tools", "/undo-steps", "/verbose", + "/save-step", "/show-step", "/show-steps", "/show-tools", "/steps", "/undo-steps", "/verbose", ] @@ -485,7 +485,7 @@ def print_help(): table.add_row("/show-steps", "Show one-line summary of all memory steps") table.add_row("/show-stats", "Show session statistics") table.add_row("/steps ", "Change max_steps for the agent") - table.add_row("/tools", "List all loaded tools") + table.add_row("/show-tools", "List all loaded tools") table.add_row("/undo-steps \[N]", "Remove last N steps from memory (default: 1)") table.add_row("/verbose", "Toggle verbose output") console.print(table) @@ -1440,7 +1440,7 @@ def get_input(): console.clear() print_banner(model_id, server_model, count_tools(agent)) continue - elif cmd == "/tools": + elif cmd == "/show-tools": print_tools(agent) continue elif cmd == "/verbose": From 6dd47762690f4e63865b63003548bc1d492c14e4 Mon Sep 17 00:00:00 2001 From: jp Date: Thu, 12 Feb 2026 12:44:14 -0300 Subject: [PATCH 02/14] Add repeat commands to CLI and session management functions - Introduced `/repeat ` and `/repeat-prompt ` commands to run prompts multiple times with fresh agent context. - Implemented `save_session_to_dict` and `load_session_from_dict` functions for in-memory session state management. --- CLI.md | 2 + src/smolagents/bp_cli.py | 93 +++++++++++++++++++++++++++++++++++- src/smolagents/bp_session.py | 72 ++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) diff --git a/CLI.md b/CLI.md index 1de66641a..fdd2bd68a 100644 --- a/CLI.md +++ b/CLI.md @@ -131,6 +131,8 @@ Use `prompt_toolkit` for: | `/load-instructions` | Load agent instruction files into next prompt | | `/plan [on\|off\|N]` | Toggle or set planning interval (default: 22) | | `/pwd` | Show current working directory | +| `/repeat ` | Run the same prompt N times, each on a fresh agent with current context | +| `/repeat-prompt ` | Run a prompt file N times, each on a fresh agent with current context | | `/run ` | Execute a Python script | | `/save ` | Save the last answer to a file | | `/save-step ` | Save full content of step N to a file | diff --git a/src/smolagents/bp_cli.py b/src/smolagents/bp_cli.py index 659f25869..c7a26d854 100644 --- a/src/smolagents/bp_cli.py +++ b/src/smolagents/bp_cli.py @@ -449,7 +449,7 @@ def print_banner(model_id: str, server_model: str, tool_count: int): "/auto-approve", "/cd", "/clear", "/compress", "/compression", "/compression-keep-recent-steps", "/compression-max-uncompressed-steps", "/compression-model", "/exit", "/file", "/help", - "/load-instructions", "/plan", "/pwd", "/run", "/save", + "/load-instructions", "/plan", "/pwd", "/repeat", "/repeat-prompt", "/run", "/save", "/session-load", "/session-save", "/show-compression-stats", "/show-memory-stats", "/show-stats", "/save-step", "/show-step", "/show-steps", "/show-tools", "/steps", "/undo-steps", "/verbose", @@ -474,6 +474,8 @@ def print_help(): table.add_row("/load-instructions", "Load agent instruction files into next prompt") table.add_row("/plan \[on|off|N]", "Toggle or set planning interval (default: 22)") table.add_row("/pwd", "Show current working directory") + table.add_row("/repeat ", "Run the same prompt N times, each on a fresh agent with current context") + table.add_row("/repeat-prompt ", "Run a prompt file N times, each on a fresh agent with current context") table.add_row("/run ", "Execute a Python script in the agent's executor") table.add_row("/save ", "Save the last answer to a file") table.add_row("/save-step ", "Save full content of step N to a file") @@ -1273,6 +1275,64 @@ def cmd_undo(agent, args: str): console.print(f"[yellow]Only {actual} of {n} requested steps were removable (protected system prompt steps).[/]") +def cmd_repeat(agent, model, n, prompt_text, session_stats, verbose, instructions, first_turn, browser_enabled): + """Run a prompt N times, each on a fresh agent with snapshotted context.""" + from smolagents.bp_session import load_session_from_dict, save_session_to_dict + from smolagents.monitoring import LogLevel + + # Snapshot current agent state (in-memory, not to file) + snapshot = save_session_to_dict(agent, session_stats) + + # Save current working directory + original_folder = os.getcwd() + + completed = 0 + errors = 0 + + for i in range(1, n + 1): + console.print(Rule(f"[bold cyan] Cycle {i}/{n} [/]", style="cyan")) + try: + # Restore working directory + os.chdir(original_folder) + + # Create fresh agent and restore snapshot + cycle_agent = build_agent(model, approval_callback=interactive_approval_callback, browser_enabled=browser_enabled) + load_session_from_dict(snapshot, cycle_agent) + + # Prepare prompt (prepend instructions on first_turn only) + task_text = prepend_instructions(prompt_text, instructions) if first_turn else prompt_text + + # Run + _spinner.start() + start_time = time.time() + if verbose: + cycle_agent.logger.level = LogLevel.INFO + else: + cycle_agent.logger.level = LogLevel.ERROR + result = cycle_agent.run(task_text, reset=False) + _spinner.stop() + elapsed = time.time() - start_time + + completed += 1 + console.print() + console.print(Markdown(str(result))) + console.print() + console.print(f"[dim]Cycle {i}/{n} completed in {elapsed:.1f}s[/]") + + except KeyboardInterrupt: + _spinner.stop() + console.print(f"\n[yellow]Interrupted at cycle {i}/{n}.[/]") + break + except Exception as e: + _spinner.stop() + errors += 1 + console.print(f"\n[bold red]Cycle {i}/{n} error:[/] {e}") + + # Summary + console.print(Rule(style="cyan")) + console.print(f"[bold]Repeat summary:[/] {completed} completed, {errors} errors out of {n} cycles") + + def _shutdown_browser(agent): """Shut down the browser manager if one exists on the agent.""" manager = getattr(agent, "_browser_manager", None) @@ -1537,6 +1597,37 @@ def get_input(): elif cmd == "/undo-steps": cmd_undo(agent, cmd_args) continue + elif cmd == "/repeat": + parts = cmd_args.strip().split(None, 1) + if len(parts) < 2: + console.print("[yellow]Usage: /repeat [/]") + continue + try: + repeat_n = int(parts[0]) + if repeat_n < 1: + raise ValueError + except ValueError: + console.print("[red]N must be a positive integer. Usage: /repeat [/]") + continue + cmd_repeat(agent, model, repeat_n, parts[1], session_stats, verbose, instructions, first_turn, browser_enabled) + continue + elif cmd == "/repeat-prompt": + parts = cmd_args.strip().split(None, 1) + if len(parts) < 2: + console.print("[yellow]Usage: /repeat-prompt [/]") + continue + try: + repeat_n = int(parts[0]) + if repeat_n < 1: + raise ValueError + except ValueError: + console.print("[red]N must be a positive integer. Usage: /repeat-prompt [/]") + continue + file_content = load_file_as_prompt(parts[1]) + if file_content is None: + continue + cmd_repeat(agent, model, repeat_n, file_content, session_stats, verbose, instructions, first_turn, browser_enabled) + continue elif cmd == "/session-save": cmd_session_save(agent, session_stats, cmd_args) continue diff --git a/src/smolagents/bp_session.py b/src/smolagents/bp_session.py index e4a3f02ae..c55d0e8d2 100644 --- a/src/smolagents/bp_session.py +++ b/src/smolagents/bp_session.py @@ -268,6 +268,78 @@ def deserialize_step(data: dict) -> MemoryStep: # --------------------------------------------------------------------------- +def save_session_to_dict(agent, session_stats: dict) -> dict: + """Snapshot agent state to an in-memory dict. + + Same serialization as save_session() but returns a dict instead of writing to a file. + + Args: + agent: The CodeAgent instance whose state to save. + session_stats: Session statistics dict (turns, time, tokens). + + Returns: + Serialized session payload as a dict. + """ + steps = [serialize_step(step) for step in agent.memory.steps] + + return { + "version": SESSION_VERSION, + "saved_at": datetime.now(timezone.utc).isoformat(), + "agent_state": { + "system_prompt": agent.memory.system_prompt.system_prompt, + "next_actionstep_id": agent._next_actionstep_id, + "last_plan_step": agent._last_plan_step, + }, + "session_stats": dict(session_stats), + "monitor_state": { + "total_input_token_count": agent.monitor.total_input_token_count, + "total_output_token_count": agent.monitor.total_output_token_count, + }, + "steps": steps, + } + + +def load_session_from_dict(payload: dict, agent) -> dict: + """Restore agent state from an in-memory dict. + + Same deserialization as load_session() but reads from a dict instead of a file. + + Args: + payload: Serialized session payload (as returned by save_session_to_dict). + agent: The CodeAgent instance to restore into. + + Returns: + Restored session_stats dict. + + Raises: + ValueError: If the payload version is unsupported. + """ + version = payload.get("version") + if version != SESSION_VERSION: + raise ValueError(f"Unsupported session version: {version} (expected {SESSION_VERSION})") + + # Restore agent memory + agent_state = payload.get("agent_state", {}) + agent.memory.system_prompt = SystemPromptStep(system_prompt=agent_state.get("system_prompt", "")) + agent.memory.steps = [deserialize_step(s) for s in payload.get("steps", [])] + + # Restore agent counters + agent._next_actionstep_id = agent_state.get("next_actionstep_id", 1) + agent._last_plan_step = agent_state.get("last_plan_step", 0) + + # Restore monitor token counts + monitor_state = payload.get("monitor_state", {}) + agent.monitor.total_input_token_count = monitor_state.get("total_input_token_count", 0) + agent.monitor.total_output_token_count = monitor_state.get("total_output_token_count", 0) + + return payload.get("session_stats", { + "turns": 0, + "total_time": 0.0, + "total_input_tokens": 0, + "total_output_tokens": 0, + }) + + def save_session(filepath: str, agent, session_stats: dict) -> int: """Save an entire agent session to a JSON file. From 2430dbf79af7c342b1fd197505fa0cc1f9cd8f03 Mon Sep 17 00:00:00 2001 From: jp Date: Thu, 12 Feb 2026 13:02:03 -0300 Subject: [PATCH 03/14] Rename CLI commands: change `/file` to `/run-prompt` and `/run` to `/run-py` --- CLI.md | 4 ++-- src/smolagents/bp_cli.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CLI.md b/CLI.md index fdd2bd68a..4a24e437c 100644 --- a/CLI.md +++ b/CLI.md @@ -126,14 +126,14 @@ Use `prompt_toolkit` for: | `/compression-max-uncompressed-steps ` | Change max_uncompressed_steps | | `/compression-model ` | Switch compression model | | `/exit` | Exit the REPL | -| `/file ` | Load a file's content as the prompt | | `/help` | Show available commands and brief descriptions | | `/load-instructions` | Load agent instruction files into next prompt | | `/plan [on\|off\|N]` | Toggle or set planning interval (default: 22) | | `/pwd` | Show current working directory | | `/repeat ` | Run the same prompt N times, each on a fresh agent with current context | | `/repeat-prompt ` | Run a prompt file N times, each on a fresh agent with current context | -| `/run ` | Execute a Python script | +| `/run-prompt ` | Load a file's content as the prompt | +| `/run-py ` | Execute a Python script | | `/save ` | Save the last answer to a file | | `/save-step ` | Save full content of step N to a file | | `/session-load ` | Load a session from a JSON file | diff --git a/src/smolagents/bp_cli.py b/src/smolagents/bp_cli.py index c7a26d854..cd6c188a1 100644 --- a/src/smolagents/bp_cli.py +++ b/src/smolagents/bp_cli.py @@ -448,8 +448,8 @@ def print_banner(model_id: str, server_model: str, tool_count: int): SLASH_COMMANDS = [ "/auto-approve", "/cd", "/clear", "/compress", "/compression", "/compression-keep-recent-steps", "/compression-max-uncompressed-steps", - "/compression-model", "/exit", "/file", "/help", - "/load-instructions", "/plan", "/pwd", "/repeat", "/repeat-prompt", "/run", "/save", + "/compression-model", "/exit", "/help", + "/load-instructions", "/plan", "/pwd", "/repeat", "/repeat-prompt", "/run-prompt", "/run-py", "/save", "/session-load", "/session-save", "/show-compression-stats", "/show-memory-stats", "/show-stats", "/save-step", "/show-step", "/show-steps", "/show-tools", "/steps", "/undo-steps", "/verbose", @@ -469,14 +469,14 @@ def print_help(): table.add_row("/compression-max-uncompressed-steps ", "Change max_uncompressed_steps") table.add_row("/compression-model ", "Switch compression model") table.add_row("/exit", "Exit the REPL") - table.add_row("/file ", "Load a file's content as the prompt") table.add_row("/help", "Show this help message") table.add_row("/load-instructions", "Load agent instruction files into next prompt") table.add_row("/plan \[on|off|N]", "Toggle or set planning interval (default: 22)") table.add_row("/pwd", "Show current working directory") table.add_row("/repeat ", "Run the same prompt N times, each on a fresh agent with current context") table.add_row("/repeat-prompt ", "Run a prompt file N times, each on a fresh agent with current context") - table.add_row("/run ", "Execute a Python script in the agent's executor") + table.add_row("/run-prompt ", "Load a file's content as the prompt") + table.add_row("/run-py ", "Execute a Python script in the agent's executor") table.add_row("/save ", "Save the last answer to a file") table.add_row("/save-step ", "Save full content of step N to a file") table.add_row("/session-load ", "Load a session from a JSON file") @@ -618,7 +618,7 @@ def load_file_as_prompt(args: str) -> str | None: """Load a file's content to use as a prompt.""" filepath = args.strip() if not filepath: - console.print("[yellow]Usage: /file [/]") + console.print("[yellow]Usage: /run-prompt [/]") return None filepath = os.path.expanduser(filepath) if not os.path.isabs(filepath): @@ -658,7 +658,7 @@ def run_script(agent, args: str): """Execute a Python script in the agent's executor.""" filepath = args.strip() if not filepath: - console.print("[yellow]Usage: /run [/]") + console.print("[yellow]Usage: /run-py [/]") return filepath = os.path.expanduser(filepath) if not os.path.isabs(filepath): @@ -1514,7 +1514,7 @@ def get_input(): elif cmd == "/show-stats": print_stats(session_stats) continue - elif cmd == "/file": + elif cmd == "/run-prompt": file_content = load_file_as_prompt(cmd_args) if file_content: text = file_content @@ -1524,7 +1524,7 @@ def get_input(): elif cmd == "/steps": change_steps(agent, cmd_args) continue - elif cmd == "/run": + elif cmd == "/run-py": run_script(agent, cmd_args) continue elif cmd == "/cd": From 0d58aac4a15606987582c46f6ce3a3332569a80c Mon Sep 17 00:00:00 2001 From: jp Date: Thu, 12 Feb 2026 14:42:48 -0300 Subject: [PATCH 04/14] Add model_id to system prompts and update git commit guidelines in YAML --- src/smolagents/agents.py | 2 ++ src/smolagents/prompts/code_agent.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/smolagents/agents.py b/src/smolagents/agents.py index b8f104a5a..12ad2b0f9 100644 --- a/src/smolagents/agents.py +++ b/src/smolagents/agents.py @@ -1431,6 +1431,7 @@ def initialize_system_prompt(self) -> str: "tools": self.tools, "managed_agents": self.managed_agents, "custom_instructions": self.instructions, + "model_id": getattr(self.model, 'model_id', 'unknown') or 'unknown', }, ) return system_prompt @@ -1810,6 +1811,7 @@ def initialize_system_prompt(self) -> str: "custom_instructions": self.instructions, "code_block_opening_tag": self.code_block_tags[0], "code_block_closing_tag": self.code_block_tags[1], + "model_id": getattr(self.model, 'model_id', 'unknown') or 'unknown', }, ) return system_prompt diff --git a/src/smolagents/prompts/code_agent.yaml b/src/smolagents/prompts/code_agent.yaml index 718cdb7ba..e64b11ff2 100644 --- a/src/smolagents/prompts/code_agent.yaml +++ b/src/smolagents/prompts/code_agent.yaml @@ -151,6 +151,8 @@ system_prompt: |- 17. In python, do not use global nor globals() as they are not available in this environment. 18. Do not use the assertion command for testing. Print the result instead of raising an exception. 19. When reading files, try to not read more than 50 lines at once, unless strictly necessary. This is to avoid overloading your context window. + 20. When making git commits (via run_os_command or any other means), always include the following co-authorship trailer at the end of the commit message: + Co-Authored-By: BPSA using {{model_id}} Any final output that you would like to give such as "my name is Assistant" should be done via a python code block with final_answer("my name is Assistant"). From 5a891af04c252b885485f6c2b742a29f628ded9f Mon Sep 17 00:00:00 2001 From: jp Date: Fri, 13 Feb 2026 09:21:49 -0300 Subject: [PATCH 05/14] Introduce inject_tree function and update CLI commands to utilize it. --- src/smolagents/bp_ad_infinitum.py | 21 +++++---------------- src/smolagents/bp_cli.py | 15 +++++++++++++++ src/smolagents/bp_thinkers.py | 11 ++--------- src/smolagents/bp_tools.py | 16 ++++++++++++++++ 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/smolagents/bp_ad_infinitum.py b/src/smolagents/bp_ad_infinitum.py index 6cdbe7754..7caae22ca 100644 --- a/src/smolagents/bp_ad_infinitum.py +++ b/src/smolagents/bp_ad_infinitum.py @@ -29,7 +29,7 @@ BPSA_PLAN_INTERVAL - Planning interval (default: None = off) BPSA_MAX_STEPS - Max steps per agent run (default: 200) BPSA_COOLDOWN - Seconds to wait between cycles (default: 0) - BPSA_INJECT_FOLDER - Inject directory tree (default: false, true = cwd, or a path) + BPSA_INJECT_FOLDER - Inject directory tree (default: true = cwd, false = off, or a path) """ import glob @@ -177,19 +177,8 @@ def get_int_env(name: str, default: int) -> int: def inject_tree(folder: str) -> str: """Generate directory tree string to append to task prompts.""" - from smolagents.bp_tools import list_directory_tree - tree = list_directory_tree(folder_path=folder, add_function_signatures=True) - return ( - "\nThis is the result of list_directory_tree:\n\n" - + tree - + "\n\n" - "The contents of is VERY important to you. " - "From , you can get a general view/current state of the project:\n" - "* From the md files, if they exist, you can find the existing section titles " - "and have a general idea of the md file contents.\n" - "* For source code files, if they exist, you can find class and method names " - "so you can also develop a general idea of their contents.\n" - ) + from smolagents.bp_tools import inject_tree as _inject_tree + return _inject_tree(folder) def run_script(task: TaskItem) -> subprocess.CompletedProcess: @@ -368,9 +357,9 @@ def main(): max_steps = get_int_env("BPSA_MAX_STEPS", 200) cooldown = get_int_env("BPSA_COOLDOWN", 0) tree_folder_raw = get_env("BPSA_INJECT_FOLDER") - if tree_folder_raw is None or tree_folder_raw.lower() == "false": + if tree_folder_raw is not None and tree_folder_raw.lower() == "false": tree_folder = None - elif tree_folder_raw.lower() == "true": + elif tree_folder_raw is None or tree_folder_raw.lower() == "true": tree_folder = os.getcwd() else: tree_folder = tree_folder_raw diff --git a/src/smolagents/bp_cli.py b/src/smolagents/bp_cli.py index cd6c188a1..f0c2b4105 100644 --- a/src/smolagents/bp_cli.py +++ b/src/smolagents/bp_cli.py @@ -1280,12 +1280,23 @@ def cmd_repeat(agent, model, n, prompt_text, session_stats, verbose, instruction from smolagents.bp_session import load_session_from_dict, save_session_to_dict from smolagents.monitoring import LogLevel + from smolagents.bp_tools import inject_tree + # Snapshot current agent state (in-memory, not to file) snapshot = save_session_to_dict(agent, session_stats) # Save current working directory original_folder = os.getcwd() + # Resolve BPSA_INJECT_FOLDER (default: true = cwd) + tree_folder_raw = get_env("BPSA_INJECT_FOLDER") + if tree_folder_raw is not None and tree_folder_raw.lower() == "false": + tree_folder = None + elif tree_folder_raw is None or tree_folder_raw.lower() == "true": + tree_folder = original_folder + else: + tree_folder = tree_folder_raw + completed = 0 errors = 0 @@ -1302,6 +1313,10 @@ def cmd_repeat(agent, model, n, prompt_text, session_stats, verbose, instruction # Prepare prompt (prepend instructions on first_turn only) task_text = prepend_instructions(prompt_text, instructions) if first_turn else prompt_text + # Inject directory tree with function signatures if configured + if tree_folder: + task_text += inject_tree(tree_folder) + # Run _spinner.start() start_time = time.time() diff --git a/src/smolagents/bp_thinkers.py b/src/smolagents/bp_thinkers.py index 94f518acc..f11426539 100644 --- a/src/smolagents/bp_thinkers.py +++ b/src/smolagents/bp_thinkers.py @@ -28,7 +28,7 @@ source_code_to_string, string_to_source_code, run_os_command, replace_on_file, replace_on_file_with_files, get_file_size, load_string_from_file, save_string_to_file, append_string_to_file, - list_directory_tree, search_in_files, get_file_info, list_directory, + list_directory_tree, inject_tree, search_in_files, get_file_info, list_directory, extract_function_signatures, compare_files, count_lines_of_code, mkdir, delete_file, delete_directory, compare_folders, read_first_n_lines, read_last_n_lines, delete_lines_from_file] @@ -1062,14 +1062,7 @@ def run_agent_cycles( try: local_prompt = task_str if list_directory_tree_in_folder is not None: - local_prompt += "\nThis is the result of list_directory_tree:\n\n" + \ - list_directory_tree(folder_path=list_directory_tree_in_folder, add_function_signatures=add_function_signatures) + \ - "\n\n" + \ -""" -The contents of is VERY important to you. From , you can get a general view/current state of the project: -* From the md files, if they exist, you can find the existing section titles and have a general idea of the md file contets. -* For source code files, if they exist, you can find class and method names so you can also develop a general idea of their contents. -""" + local_prompt += inject_tree(list_directory_tree_in_folder) # restore the original folder os.chdir(original_folder) local_agent = get_default_thinker_agent( diff --git a/src/smolagents/bp_tools.py b/src/smolagents/bp_tools.py index aea7ad5c3..6e876ac6b 100644 --- a/src/smolagents/bp_tools.py +++ b/src/smolagents/bp_tools.py @@ -1677,6 +1677,22 @@ def add_tree_lines(current_path, prefix="", depth=0): return "\n".join(lines) + +def inject_tree(folder: str) -> str: + """Generate directory tree string with function signatures to append to task prompts.""" + tree = list_directory_tree(folder_path=folder, add_function_signatures=True) + return ( + "\nThis is the result of list_directory_tree:\n\n" + + tree + + "\n\n" + "The contents of is VERY important to you. " + "From , you can get a general view/current state of the project:\n" + "* From the md files, if they exist, you can find the existing section titles " + "and have a general idea of the md file contents.\n" + "* For source code files, if they exist, you can find class and method names " + "so you can also develop a general idea of their contents.\n" + ) + @tool def search_in_files(folder_path: str, search_pattern: str, file_extensions: tuple = None, case_sensitive: bool = False, max_results: int = 50) -> str: From 45f0e787fb8c9bbabb8dba3b6e1654894f80b74e Mon Sep 17 00:00:00 2001 From: jp Date: Fri, 13 Feb 2026 18:35:28 -0300 Subject: [PATCH 06/14] Adds support for shell commands. --- src/smolagents/bp_cli.py | 11 +++++++++++ src/smolagents/bp_tools.py | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/smolagents/bp_cli.py b/src/smolagents/bp_cli.py index f0c2b4105..4d946002e 100644 --- a/src/smolagents/bp_cli.py +++ b/src/smolagents/bp_cli.py @@ -460,6 +460,7 @@ def print_help(): table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2)) table.add_column("Command", style="bold cyan") table.add_column("Description") + table.add_row("!", "Run an OS command directly (agent does not see the output)") table.add_row("/auto-approve \[on|off]", "Toggle or set auto-approve for tag execution") table.add_row("/cd ", "Change working directory") table.add_row("/clear", "Clear screen, reset agent and conversation history") @@ -1485,6 +1486,16 @@ def get_input(): if not text: continue + # Handle ! shell escape: run OS command directly (agent doesn't see it) + if text.startswith("!"): + shell_cmd = text[1:].strip() + if shell_cmd: + try: + subprocess.run(shell_cmd, shell=True) + except KeyboardInterrupt: + console.print("\n[dim]Command interrupted.[/]") + continue + # Handle slash commands if text.startswith("/"): cmd_parts = text.split(maxsplit=1) diff --git a/src/smolagents/bp_tools.py b/src/smolagents/bp_tools.py index 6e876ac6b..ceeda07e6 100644 --- a/src/smolagents/bp_tools.py +++ b/src/smolagents/bp_tools.py @@ -1678,8 +1678,13 @@ def add_tree_lines(current_path, prefix="", depth=0): return "\n".join(lines) +@tool def inject_tree(folder: str) -> str: - """Generate directory tree string with function signatures to append to task prompts.""" + """Generate directory tree string with function signatures to append to task prompts. + + Args: + folder: path to the folder to generate the tree from. + """ tree = list_directory_tree(folder_path=folder, add_function_signatures=True) return ( "\nThis is the result of list_directory_tree:\n\n" From c4f348cf22701e3d68109965ff13ba3b77f28d88 Mon Sep 17 00:00:00 2001 From: jp Date: Fri, 13 Feb 2026 18:50:20 -0300 Subject: [PATCH 07/14] Add !! command. --- src/smolagents/bp_cli.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/smolagents/bp_cli.py b/src/smolagents/bp_cli.py index 4d946002e..1d3f1788d 100644 --- a/src/smolagents/bp_cli.py +++ b/src/smolagents/bp_cli.py @@ -24,6 +24,7 @@ import time from dotenv import load_dotenv +from smolagents.utils import truncate_content from rich.console import Console from rich.markdown import Markdown from rich.panel import Panel @@ -461,6 +462,7 @@ def print_help(): table.add_column("Command", style="bold cyan") table.add_column("Description") table.add_row("!", "Run an OS command directly (agent does not see the output)") + table.add_row("!!", "Run an OS command; output is appended to the next prompt sent to the agent") table.add_row("/auto-approve \[on|off]", "Toggle or set auto-approve for tag execution") table.add_row("/cd ", "Change working directory") table.add_row("/clear", "Clear screen, reset agent and conversation history") @@ -1467,6 +1469,7 @@ def get_input(): return "" last_answer = None + pending_shell_outputs = [] session_stats = { "turns": 0, "total_time": 0.0, @@ -1486,6 +1489,20 @@ def get_input(): if not text: continue + # Handle !! shell escape: run OS command, output appended to next prompt + if text.startswith("!!"): + shell_cmd = text[2:].strip() + if shell_cmd: + try: + proc = subprocess.run(shell_cmd, shell=True, capture_output=True, text=True) + output = (proc.stdout or "") + (proc.stderr or "") + if output: + console.print(output, end="" if output.endswith("\n") else "\n") + pending_shell_outputs.append((shell_cmd, truncate_content(output))) + except KeyboardInterrupt: + console.print("\n[dim]Command interrupted.[/]") + continue + # Handle ! shell escape: run OS command directly (agent doesn't see it) if text.startswith("!"): shell_cmd = text[1:].strip() @@ -1699,6 +1716,13 @@ def get_input(): _spinner.start() task_text = prepend_instructions(text, instructions) if first_turn else text first_turn = False + if pending_shell_outputs: + shell_context = "\n".join( + f"\n{cmd}\n\n{out}\n" + for cmd, out in pending_shell_outputs + ) + task_text = task_text + "\n" + shell_context + pending_shell_outputs.clear() result = agent.run(task_text, reset=False) _spinner.stop() elapsed = time.time() - start_time From 3fa2af5306262242cf36e31641bd1a75735a58c9 Mon Sep 17 00:00:00 2001 From: jp Date: Fri, 13 Feb 2026 18:52:00 -0300 Subject: [PATCH 08/14] Fixes documentation regarding ! and !! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4a262c20..08ef15d77 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ $ bpsa --load-instructions # Load CLAUDE.md, AGENTS.md, etc. at startup $ bpsa --browser # Enable Playwright browser integration ``` -The REPL supports command history, tab completion for slash commands, and multi-line input via Alt+Enter. Use `/session-save ` and `/session-load ` to persist and restore sessions across restarts. +The REPL supports command history, tab completion for slash commands, and multi-line input via Alt+Enter. Use `/session-save ` and `/session-load ` to persist and restore sessions across restarts. You can also run OS commands directly from the REPL with `!` (agent does not see the output) or `!!` (output is appended to the next prompt sent to the agent). Type `/help` to see all available commands. ## CLI (`ad-infinitum`) From 85f06a339c15c0aae274a255503e04ab402f66a4 Mon Sep 17 00:00:00 2001 From: jp Date: Fri, 13 Feb 2026 18:55:15 -0300 Subject: [PATCH 09/14] Small addition to the CLI documentation. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08ef15d77..47eee5c85 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ $ bpsa --load-instructions # Load CLAUDE.md, AGENTS.md, etc. at startup $ bpsa --browser # Enable Playwright browser integration ``` -The REPL supports command history, tab completion for slash commands, and multi-line input via Alt+Enter. Use `/session-save ` and `/session-load ` to persist and restore sessions across restarts. You can also run OS commands directly from the REPL with `!` (agent does not see the output) or `!!` (output is appended to the next prompt sent to the agent). Type `/help` to see all available commands. +The REPL supports command history, tab completion for slash commands, and multi-line input via Alt+Enter. Use `/session-save ` and `/session-load ` to persist and restore sessions across restarts. You can also run OS commands directly from the REPL with `!` (agent does not see the output) or `!!` (output is appended to the next prompt sent to the agent). Type `/help` to see all available commands. You can also launch `ad-infinitum` from within the REPL via `!ad-infinitum ...`. ## CLI (`ad-infinitum`) From 1234776b03b05d05d78b2faef911c9f8ec1d3b3d Mon Sep 17 00:00:00 2001 From: jp Date: Fri, 13 Feb 2026 19:12:38 -0300 Subject: [PATCH 10/14] Add streaming !!, !!? instant analysis, /redo, /alias, and auto-save to CLI Five new features for the bpsa REPL: - Streaming !! output via line-by-line Popen (replaces buffered capture) - !!? runs a command and immediately sends output to the agent - /redo undoes last step and re-runs the previous prompt - /alias with persistence to ~/.bpsa_aliases (supports create, list, delete) - Auto-save session every N turns (BPSA_AUTOSAVE_INTERVAL, default 5) Co-Authored-By: Claude Opus 4.6 --- src/smolagents/bp_cli.py | 123 +++++++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 11 deletions(-) diff --git a/src/smolagents/bp_cli.py b/src/smolagents/bp_cli.py index 1d3f1788d..106ef9af9 100644 --- a/src/smolagents/bp_cli.py +++ b/src/smolagents/bp_cli.py @@ -446,11 +446,51 @@ def print_banner(model_id: str, server_model: str, tool_count: int): console.print("[dim]Type /help for commands, /verbose to toggle verbosity, /exit to quit.[/]\n") +def _run_shell_streaming(shell_cmd: str) -> str: + """Run a shell command, streaming output to the terminal and returning the full output.""" + output_lines = [] + try: + proc = subprocess.Popen(shell_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + for line in proc.stdout: + print(line, end="") + output_lines.append(line) + proc.wait() + except KeyboardInterrupt: + proc.kill() + proc.wait() + console.print("\n[dim]Command interrupted.[/]") + return "".join(output_lines) + + +ALIASES_FILE = os.path.expanduser("~/.bpsa_aliases") + + +def _load_aliases() -> dict: + """Load aliases from ~/.bpsa_aliases.""" + aliases = {} + if os.path.isfile(ALIASES_FILE): + with open(ALIASES_FILE) as f: + for line in f: + line = line.strip() + if line and not line.startswith("#"): + parts = line.split(None, 1) + if len(parts) == 2: + aliases[parts[0]] = parts[1] + return aliases + + +def _save_aliases(aliases: dict): + """Save aliases to ~/.bpsa_aliases.""" + with open(ALIASES_FILE, "w") as f: + for name, value in sorted(aliases.items()): + f.write(f"{name} {value}\n") + + SLASH_COMMANDS = [ - "/auto-approve", "/cd", "/clear", "/compress", "/compression", + "/alias", "/auto-approve", "/cd", "/clear", "/compress", "/compression", "/compression-keep-recent-steps", "/compression-max-uncompressed-steps", "/compression-model", "/exit", "/help", - "/load-instructions", "/plan", "/pwd", "/repeat", "/repeat-prompt", "/run-prompt", "/run-py", "/save", + "/load-instructions", "/plan", "/pwd", "/redo", "/repeat", "/repeat-prompt", "/run-prompt", "/run-py", "/save", "/session-load", "/session-save", "/show-compression-stats", "/show-memory-stats", "/show-stats", "/save-step", "/show-step", "/show-steps", "/show-tools", "/steps", "/undo-steps", "/verbose", @@ -463,6 +503,8 @@ def print_help(): table.add_column("Description") table.add_row("!", "Run an OS command directly (agent does not see the output)") table.add_row("!!", "Run an OS command; output is appended to the next prompt sent to the agent") + table.add_row("!!?", "Run an OS command and immediately send the output to the agent for analysis") + table.add_row("/alias ", "Define alias (saved to ~/.bpsa_aliases). No args=list, -d =delete") table.add_row("/auto-approve \[on|off]", "Toggle or set auto-approve for tag execution") table.add_row("/cd ", "Change working directory") table.add_row("/clear", "Clear screen, reset agent and conversation history") @@ -476,6 +518,7 @@ def print_help(): table.add_row("/load-instructions", "Load agent instruction files into next prompt") table.add_row("/plan \[on|off|N]", "Toggle or set planning interval (default: 22)") table.add_row("/pwd", "Show current working directory") + table.add_row("/redo", "Re-run the last prompt (undo last steps and run again)") table.add_row("/repeat ", "Run the same prompt N times, each on a fresh agent with current context") table.add_row("/repeat-prompt ", "Run a prompt file N times, each on a fresh agent with current context") table.add_row("/run-prompt ", "Load a file's content as the prompt") @@ -1469,7 +1512,11 @@ def get_input(): return "" last_answer = None + last_prompt = None pending_shell_outputs = [] + aliases = _load_aliases() + autosave_interval = int(get_env("BPSA_AUTOSAVE_INTERVAL", default="5")) + autosave_file = os.path.expanduser("~/.bpsa_autosave.json") session_stats = { "turns": 0, "total_time": 0.0, @@ -1489,18 +1536,29 @@ def get_input(): if not text: continue + # Expand aliases: check if first word matches an alias + first_word = text.split(None, 1)[0] if text else "" + if first_word in aliases: + rest = text[len(first_word):].lstrip() + text = aliases[first_word] + (" " + rest if rest else "") + + # Handle !!? shell escape: run OS command and immediately send to agent + if text.startswith("!!?"): + shell_cmd = text[3:].strip() + if shell_cmd: + output = _run_shell_streaming(shell_cmd) + shell_context = f"\n{shell_cmd}\n\n{truncate_content(output)}\n" + text = f"Analyze the output of the command above.\n{shell_context}" + # Fall through to agent run below + else: + continue + # Handle !! shell escape: run OS command, output appended to next prompt - if text.startswith("!!"): + elif text.startswith("!!"): shell_cmd = text[2:].strip() if shell_cmd: - try: - proc = subprocess.run(shell_cmd, shell=True, capture_output=True, text=True) - output = (proc.stdout or "") + (proc.stderr or "") - if output: - console.print(output, end="" if output.endswith("\n") else "\n") - pending_shell_outputs.append((shell_cmd, truncate_content(output))) - except KeyboardInterrupt: - console.print("\n[dim]Command interrupted.[/]") + output = _run_shell_streaming(shell_cmd) + pending_shell_outputs.append((shell_cmd, truncate_content(output))) continue # Handle ! shell escape: run OS command directly (agent doesn't see it) @@ -1529,6 +1587,39 @@ def get_input(): elif cmd == "/help": print_help() continue + elif cmd == "/alias": + args = cmd_args.strip() + if not args: + if aliases: + for name, value in sorted(aliases.items()): + console.print(f" [cyan]{name}[/] = {value}") + else: + console.print("[dim]No aliases defined. Usage: /alias [/]") + elif args.startswith("-d "): + alias_name = args[3:].strip() + if alias_name in aliases: + del aliases[alias_name] + _save_aliases(aliases) + console.print(f"[cyan]Alias '{alias_name}' deleted.[/]") + else: + console.print(f"[yellow]Alias '{alias_name}' not found.[/]") + else: + parts = args.split(None, 1) + if len(parts) < 2: + console.print("[yellow]Usage: /alias or /alias -d [/]") + else: + aliases[parts[0]] = parts[1] + _save_aliases(aliases) + console.print(f"[cyan]{parts[0]}[/] = {parts[1]}") + continue + elif cmd == "/redo": + if last_prompt is None: + console.print("[yellow]No previous prompt to redo.[/]") + continue + # Undo the steps from the last turn, then re-run + cmd_undo(agent, "") + text = last_prompt + # Fall through to agent run below elif cmd == "/clear": _shutdown_browser(agent) agent = build_agent(model, approval_callback=interactive_approval_callback, browser_enabled=browser_enabled) @@ -1714,6 +1805,7 @@ def get_input(): else: agent.logger.level = LogLevel.ERROR _spinner.start() + last_prompt = text task_text = prepend_instructions(text, instructions) if first_turn else text first_turn = False if pending_shell_outputs: @@ -1746,6 +1838,15 @@ def get_input(): print_turn_summary(turn_num, elapsed, turn_input, turn_output, agent) console.print() + # Auto-save session periodically + if autosave_interval > 0 and session_stats["turns"] % autosave_interval == 0: + try: + from smolagents.bp_session import save_session + save_session(autosave_file, agent, session_stats) + console.print(f"[dim]Auto-saved session to {autosave_file}[/]") + except Exception: + pass + except KeyboardInterrupt: _spinner.stop() console.print("\n[yellow]Interrupted.[/]") From 05bcbef87f13d3fa69c7b0d54cb77e00c167d684 Mon Sep 17 00:00:00 2001 From: jp Date: Fri, 13 Feb 2026 19:14:00 -0300 Subject: [PATCH 11/14] Update documentation for shell escapes, aliases, redo, and auto-save --- CLAUDE.md | 2 +- README.md | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ea7c55a8d..f7a794110 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,7 +55,7 @@ The project uses a `src/smolagents/` layout. All source code is under `src/smola - `prompts/` - YAML prompt templates for different agent types ### Beyond Python (BP) Extensions -- `bp_cli.py` - Interactive CLI (`bpsa`): REPL and one-shot modes with CodeAgent, slash commands, token tracking +- `bp_cli.py` - Interactive CLI (`bpsa`): REPL and one-shot modes with CodeAgent, slash commands, token tracking, shell escapes (`!`, `!!`, `!!?`), `/alias`, `/redo`, auto-save - `bp_tools.py` - Extended tool library: file I/O, source code analysis, OS commands, multi-language support (24+ languages including Pascal, PHP, C++, Java, Go, Rust) - `bp_thinkers.py` - `Thinker` agent with multi-step reasoning (thoughts/plans/code sections) - `bp_executors.py` - `LocalExecExecutor` for direct Python execution via `exec()` diff --git a/README.md b/README.md index 47eee5c85..3519841d4 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,23 @@ $ bpsa --load-instructions # Load CLAUDE.md, AGENTS.md, etc. at startup $ bpsa --browser # Enable Playwright browser integration ``` -The REPL supports command history, tab completion for slash commands, and multi-line input via Alt+Enter. Use `/session-save ` and `/session-load ` to persist and restore sessions across restarts. You can also run OS commands directly from the REPL with `!` (agent does not see the output) or `!!` (output is appended to the next prompt sent to the agent). Type `/help` to see all available commands. You can also launch `ad-infinitum` from within the REPL via `!ad-infinitum ...`. +The REPL supports command history, tab completion for slash commands, and multi-line input via Alt+Enter. Use `/session-save ` and `/session-load ` to persist and restore sessions across restarts. You can also launch `ad-infinitum` from within the REPL via `!ad-infinitum ...`. Type `/help` to see all available commands. + +#### Shell commands from the REPL + +| Prefix | Description | +|--------|-------------| +| `!` | Run an OS command directly (agent does not see the output) | +| `!!` | Run an OS command with streaming output; output is appended to the next prompt sent to the agent | +| `!!?` | Run an OS command and immediately send the output to the agent for analysis | + +#### Aliases + +Define command aliases with `/alias ` (e.g., `/alias gs !!git status`). Aliases are saved to `~/.bpsa_aliases` and persist across sessions. Use `/alias` to list all and `/alias -d ` to delete. + +#### Auto-save + +Sessions are automatically saved every 5 turns to `~/.bpsa_autosave.json`. Configure the interval with the `BPSA_AUTOSAVE_INTERVAL` environment variable (set to 0 to disable). ## CLI (`ad-infinitum`) From 80cc4e599620e0b91e9381062e41fc6f6dfe1d6c Mon Sep 17 00:00:00 2001 From: jp Date: Sat, 14 Feb 2026 08:19:48 -0300 Subject: [PATCH 12/14] Replaces !!? by !!! --- CLAUDE.md | 2 +- README.md | 2 +- src/smolagents/bp_cli.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f7a794110..da3a0fd5a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,7 +55,7 @@ The project uses a `src/smolagents/` layout. All source code is under `src/smola - `prompts/` - YAML prompt templates for different agent types ### Beyond Python (BP) Extensions -- `bp_cli.py` - Interactive CLI (`bpsa`): REPL and one-shot modes with CodeAgent, slash commands, token tracking, shell escapes (`!`, `!!`, `!!?`), `/alias`, `/redo`, auto-save +- `bp_cli.py` - Interactive CLI (`bpsa`): REPL and one-shot modes with CodeAgent, slash commands, token tracking, shell escapes (`!`, `!!`, `!!!`), `/alias`, `/redo`, auto-save - `bp_tools.py` - Extended tool library: file I/O, source code analysis, OS commands, multi-language support (24+ languages including Pascal, PHP, C++, Java, Go, Rust) - `bp_thinkers.py` - `Thinker` agent with multi-step reasoning (thoughts/plans/code sections) - `bp_executors.py` - `LocalExecExecutor` for direct Python execution via `exec()` diff --git a/README.md b/README.md index 3519841d4..bb180dbad 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ The REPL supports command history, tab completion for slash commands, and multi- |--------|-------------| | `!` | Run an OS command directly (agent does not see the output) | | `!!` | Run an OS command with streaming output; output is appended to the next prompt sent to the agent | -| `!!?` | Run an OS command and immediately send the output to the agent for analysis | +| `!!!` | Run an OS command and immediately send the output to the agent for analysis | #### Aliases diff --git a/src/smolagents/bp_cli.py b/src/smolagents/bp_cli.py index 106ef9af9..ccf51f27c 100644 --- a/src/smolagents/bp_cli.py +++ b/src/smolagents/bp_cli.py @@ -503,7 +503,7 @@ def print_help(): table.add_column("Description") table.add_row("!", "Run an OS command directly (agent does not see the output)") table.add_row("!!", "Run an OS command; output is appended to the next prompt sent to the agent") - table.add_row("!!?", "Run an OS command and immediately send the output to the agent for analysis") + table.add_row("!!!", "Run an OS command and immediately send the output to the agent for analysis") table.add_row("/alias ", "Define alias (saved to ~/.bpsa_aliases). No args=list, -d =delete") table.add_row("/auto-approve \[on|off]", "Toggle or set auto-approve for tag execution") table.add_row("/cd ", "Change working directory") @@ -1542,8 +1542,8 @@ def get_input(): rest = text[len(first_word):].lstrip() text = aliases[first_word] + (" " + rest if rest else "") - # Handle !!? shell escape: run OS command and immediately send to agent - if text.startswith("!!?"): + # Handle !!! shell escape: run OS command and immediately send to agent + if text.startswith("!!!"): shell_cmd = text[3:].strip() if shell_cmd: output = _run_shell_streaming(shell_cmd) From 729244e555cae6182afcaca74abd26ce68dff8f8 Mon Sep 17 00:00:00 2001 From: jp Date: Sat, 14 Feb 2026 09:11:53 -0300 Subject: [PATCH 13/14] Better maxsteps treatment. --- src/smolagents/agents.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/smolagents/agents.py b/src/smolagents/agents.py index 12ad2b0f9..ce34a6c36 100644 --- a/src/smolagents/agents.py +++ b/src/smolagents/agents.py @@ -687,19 +687,23 @@ def _finalize_step(self, memory_step: ActionStep | PlanningStep): def _handle_max_steps_reached(self, task: str) -> Any: action_step_start_time = time.time() - final_answer = self.provide_final_answer(task) + # BP: replaced forced LLM final answer with a static message to save tokens + # final_answer = self.provide_final_answer(task) + message = f"Max steps reached ({self.max_steps}/{self.max_steps}) - should I continue?" final_memory_step = ActionStep( step_number=self.step_number, error=AgentMaxStepsError("Reached max steps.", self.logger), timing=Timing(start_time=action_step_start_time, end_time=time.time()), - token_usage=final_answer.token_usage, + # token_usage=final_answer.token_usage, actionstep_id=self._next_actionstep_id, ) self._next_actionstep_id += 1 - final_memory_step.action_output = final_answer.content + # final_memory_step.action_output = final_answer.content + final_memory_step.action_output = message self._finalize_step(final_memory_step) self.memory.steps.append(final_memory_step) - return final_answer.content + # return final_answer.content + return message def _generate_planning_step( self, task, is_first_step: bool, step: int From 1b696a01fa3184fdc281f40bf210af81458ffbfb Mon Sep 17 00:00:00 2001 From: jp Date: Sat, 14 Feb 2026 09:18:26 -0300 Subject: [PATCH 14/14] Documentation updates. --- CLI.md | 10 +++++++++- src/smolagents/bp_cli.py | 10 +++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CLI.md b/CLI.md index 4a24e437c..0016e0384 100644 --- a/CLI.md +++ b/CLI.md @@ -108,6 +108,14 @@ Use `prompt_toolkit` for: - Ctrl+C handling (cancel current input, not exit) - Autocomplete (slash commands) +### Shell Escapes + +| Command | Description | +|---------|-------------| +| `!` | Run an OS command directly (agent does not see the output) | +| `!!` | Run an OS command; output is appended to the next prompt sent to the agent | +| `!!!` | Run an OS command and immediately send the output to the agent for analysis | + ### Step Display - Show intermediate code/thoughts as they happen (streaming) @@ -143,7 +151,7 @@ Use `prompt_toolkit` for: | `/show-stats` | Show session statistics (token usage, time) | | `/show-step ` | Show full content of a specific step | | `/show-steps` | Show one-line summary of all memory steps | -| `/steps ` | Change max_steps for the agent | +| `/set-max-steps ` | Change max_steps for the agent | | `/show-tools` | List all loaded tools | | `/undo-steps [N]` | Remove last N steps from memory (default: 1) | | `/verbose` | Toggle verbose output | diff --git a/src/smolagents/bp_cli.py b/src/smolagents/bp_cli.py index ccf51f27c..0f4e917b5 100644 --- a/src/smolagents/bp_cli.py +++ b/src/smolagents/bp_cli.py @@ -493,7 +493,7 @@ def _save_aliases(aliases: dict): "/load-instructions", "/plan", "/pwd", "/redo", "/repeat", "/repeat-prompt", "/run-prompt", "/run-py", "/save", "/session-load", "/session-save", "/show-compression-stats", "/show-memory-stats", "/show-stats", - "/save-step", "/show-step", "/show-steps", "/show-tools", "/steps", "/undo-steps", "/verbose", + "/save-step", "/set-max-steps", "/show-step", "/show-steps", "/show-tools", "/undo-steps", "/verbose", ] @@ -532,7 +532,7 @@ def print_help(): table.add_row("/show-step ", "Show full content of a specific step") table.add_row("/show-steps", "Show one-line summary of all memory steps") table.add_row("/show-stats", "Show session statistics") - table.add_row("/steps ", "Change max_steps for the agent") + table.add_row("/set-max-steps ", "Change max_steps for the agent") table.add_row("/show-tools", "List all loaded tools") table.add_row("/undo-steps \[N]", "Remove last N steps from memory (default: 1)") table.add_row("/verbose", "Toggle verbose output") @@ -687,7 +687,7 @@ def change_steps(agent, args: str): args = args.strip() if not args: console.print(f"[cyan]Current max_steps: {agent.max_steps}[/]") - console.print("[dim]Usage: /steps [/]") + console.print("[dim]Usage: /set-max-steps [/]") return try: n = int(args) @@ -697,7 +697,7 @@ def change_steps(agent, args: str): agent.max_steps = n console.print(f"[green]max_steps set to {n}[/]") except ValueError: - console.print("[red]Invalid number. Usage: /steps [/]") + console.print("[red]Invalid number. Usage: /set-max-steps [/]") def run_script(agent, args: str): @@ -1655,7 +1655,7 @@ def get_input(): # Fall through to agent run else: continue - elif cmd == "/steps": + elif cmd == "/set-max-steps": change_steps(agent, cmd_args) continue elif cmd == "/run-py":