diff --git a/src/timmy/tools.py b/src/timmy/tools.py index ec8db02..3fe1ef2 100644 --- a/src/timmy/tools.py +++ b/src/timmy/tools.py @@ -472,26 +472,8 @@ def consult_grok(query: str) -> str: return response -def create_full_toolkit(base_dir: str | Path | None = None): - """Create a full toolkit with all available tools (for the orchestrator). - - Includes: web search, file read/write, shell commands, python execution, - memory search for contextual recall, and Grok consultation. - """ - if not _AGNO_TOOLS_AVAILABLE: - # Return None when tools aren't available (tests) - return None - - from timmy.tool_safety import DANGEROUS_TOOLS - - toolkit = Toolkit( - name="full", - ) - # Set requires_confirmation_tools AFTER construction (avoids agno WARNING - # about tools not yet registered) but BEFORE register() calls (so each - # Function gets requires_confirmation=True). Fixes #79. - toolkit.requires_confirmation_tools = list(DANGEROUS_TOOLS) - +def _register_core_tools(toolkit: Toolkit, base_path: Path) -> None: + """Register core execution and file tools.""" # Python execution python_tools = PythonTools() toolkit.register(python_tools.run_python_code, name="python") @@ -500,10 +482,7 @@ def create_full_toolkit(base_dir: str | Path | None = None): shell_tools = ShellTools() toolkit.register(shell_tools.run_shell_command, name="shell") - # File operations - use repo_root from settings - from config import settings - - base_path = Path(base_dir) if base_dir else Path(settings.repo_root) + # File operations file_tools = FileTools(base_dir=base_path) toolkit.register(_make_smart_read_file(file_tools), name="read_file") toolkit.register(file_tools.save_file, name="write_file") @@ -512,7 +491,9 @@ def create_full_toolkit(base_dir: str | Path | None = None): # Calculator — exact arithmetic (never let the LLM guess) toolkit.register(calculator, name="calculator") - # Grok consultation — premium frontier reasoning (opt-in) + +def _register_grok_tool(toolkit: Toolkit) -> None: + """Register Grok consultation tool if available.""" try: from timmy.backends import grok_available @@ -523,7 +504,9 @@ def create_full_toolkit(base_dir: str | Path | None = None): logger.warning("Tool execution failed (Grok registration): %s", exc) logger.debug("Grok tool not available") - # Memory search, write, and forget — persistent recall across all channels + +def _register_memory_tools(toolkit: Toolkit) -> None: + """Register memory search, write, and forget tools.""" try: from timmy.semantic_memory import memory_forget, memory_read, memory_search, memory_write @@ -535,7 +518,9 @@ def create_full_toolkit(base_dir: str | Path | None = None): logger.warning("Tool execution failed (Memory tools registration): %s", exc) logger.debug("Memory tools not available") - # Agentic loop — background multi-step task execution + +def _register_agentic_loop_tool(toolkit: Toolkit) -> None: + """Register agentic loop tool for background multi-step task execution.""" try: from timmy.agentic_loop import run_agentic_loop @@ -582,7 +567,9 @@ def create_full_toolkit(base_dir: str | Path | None = None): logger.warning("Tool execution failed (plan_and_execute registration): %s", exc) logger.debug("plan_and_execute tool not available") - # System introspection - query runtime environment (sovereign self-knowledge) + +def _register_introspection_tools(toolkit: Toolkit) -> None: + """Register system introspection tools for runtime environment queries.""" try: from timmy.tools_intro import ( check_ollama_health, @@ -599,7 +586,9 @@ def create_full_toolkit(base_dir: str | Path | None = None): logger.warning("Tool execution failed (Introspection tools registration): %s", exc) logger.debug("Introspection tools not available") - # Inter-agent delegation - dispatch tasks to swarm agents + +def _register_delegation_tools(toolkit: Toolkit) -> None: + """Register inter-agent delegation tools.""" try: from timmy.tools_delegation import delegate_task, delegate_to_kimi, list_swarm_agents @@ -610,6 +599,34 @@ def create_full_toolkit(base_dir: str | Path | None = None): logger.warning("Tool execution failed (Delegation tools registration): %s", exc) logger.debug("Delegation tools not available") + +def create_full_toolkit(base_dir: str | Path | None = None): + """Create a full toolkit with all available tools (for the orchestrator). + + Includes: web search, file read/write, shell commands, python execution, + memory search for contextual recall, and Grok consultation. + """ + if not _AGNO_TOOLS_AVAILABLE: + # Return None when tools aren't available (tests) + return None + + from timmy.tool_safety import DANGEROUS_TOOLS + + toolkit = Toolkit(name="full") + # Set requires_confirmation_tools AFTER construction (avoids agno WARNING + # about tools not yet registered) but BEFORE register() calls (so each + # Function gets requires_confirmation=True). Fixes #79. + toolkit.requires_confirmation_tools = list(DANGEROUS_TOOLS) + + base_path = Path(base_dir) if base_dir else Path(settings.repo_root) + + _register_core_tools(toolkit, base_path) + _register_grok_tool(toolkit) + _register_memory_tools(toolkit) + _register_agentic_loop_tool(toolkit) + _register_introspection_tools(toolkit) + _register_delegation_tools(toolkit) + # Gitea issue management is now provided by the gitea-mcp server # (wired in as MCPTools in agent.py, not registered here) @@ -719,13 +736,9 @@ get_tools_for_persona = get_tools_for_agent PERSONA_TOOLKITS = AGENT_TOOLKITS -def get_all_available_tools() -> dict[str, dict]: - """Get a catalog of all available tools and their descriptions. - - Returns: - Dict mapping tool categories to their tools and descriptions. - """ - catalog = { +def _core_tool_catalog() -> dict: + """Return core file and execution tools catalog entries.""" + return { "shell": { "name": "Shell Commands", "description": "Execute shell commands (sandboxed)", @@ -751,16 +764,39 @@ def get_all_available_tools() -> dict[str, dict]: "description": "List files in a directory", "available_in": ["echo", "seer", "forge", "quill", "mace", "helm", "orchestrator"], }, + } + + +def _analysis_tool_catalog() -> dict: + """Return analysis and calculation tools catalog entries.""" + return { "calculator": { "name": "Calculator", "description": "Evaluate mathematical expressions with exact results", "available_in": ["orchestrator"], }, + } + + +def _ai_tool_catalog() -> dict: + """Return AI assistant and frontier reasoning tools catalog entries.""" + return { "consult_grok": { "name": "Consult Grok", "description": "Premium frontier reasoning via xAI Grok (opt-in, Lightning-payable)", "available_in": ["orchestrator"], }, + "aider": { + "name": "Aider AI Assistant", + "description": "Local AI coding assistant using Ollama (qwen3.5:latest or deepseek-coder)", + "available_in": ["forge", "orchestrator"], + }, + } + + +def _introspection_tool_catalog() -> dict: + """Return system introspection tools catalog entries.""" + return { "get_system_info": { "name": "System Info", "description": "Introspect runtime environment - discover model, Python version, config", @@ -776,11 +812,12 @@ def get_all_available_tools() -> dict[str, dict]: "description": "Check status of memory tiers (hot memory, vault)", "available_in": ["orchestrator"], }, - "aider": { - "name": "Aider AI Assistant", - "description": "Local AI coding assistant using Ollama (qwen3.5:latest or deepseek-coder)", - "available_in": ["forge", "orchestrator"], - }, + } + + +def _experiment_tool_catalog() -> dict: + """Return ML experiment tools catalog entries.""" + return { "prepare_experiment": { "name": "Prepare Experiment", "description": "Clone autoresearch repo and run data preparation for ML experiments", @@ -798,6 +835,9 @@ def get_all_available_tools() -> dict[str, dict]: }, } + +def _import_creative_catalogs(catalog: dict) -> None: + """Import and merge creative tool catalogs from creative module.""" # ── Git tools ───────────────────────────────────────────────────────────── try: from creative.tools.git_tools import GIT_TOOL_CATALOG @@ -876,4 +916,18 @@ def get_all_available_tools() -> dict[str, dict]: except ImportError: pass + +def get_all_available_tools() -> dict[str, dict]: + """Get a catalog of all available tools and their descriptions. + + Returns: + Dict mapping tool categories to their tools and descriptions. + """ + catalog = {} + catalog.update(_core_tool_catalog()) + catalog.update(_analysis_tool_catalog()) + catalog.update(_ai_tool_catalog()) + catalog.update(_introspection_tool_catalog()) + catalog.update(_experiment_tool_catalog()) + _import_creative_catalogs(catalog) return catalog