# Tool System

Tools are the agent's actions. All tools implement the `Tool` ABC from `navi/tools/base.py`.

## Two tiers

### Built-in tools (`navi/tools/`)

Registered in `build_default_registries()` as builtins. Never removed on hot-reload.

| Tool | Name | Description |
|---|---|---|
| `WebSearchTool` | `web_search` | DuckDuckGo search |
| `WebViewTool` | `web_view` | Fetch and render a URL |
| `FilesystemTool` | `filesystem` | Read/write/list local files (path restrictions via config) |
| `HttpRequestTool` | `http_request` | Generic HTTP client (GET/POST/etc.) |
| `CodeExecTool` | `code_exec` | Execute Python in a subprocess sandbox |
| `TerminalTool` | `terminal` | Run shell commands (command allowlist via config) |
| `SshExecTool` | `ssh_exec` | SSH into remote hosts; connection pool keyed by session ID |
| `ImageViewTool` | `image_view` | Load image from path/URL → returns base64 for multimodal LLM |
| `TodoTool` | `todo` | Per-session task checklist (set/update/read) |
| `ScratchpadTool` | `scratchpad` | Per-session named working notes (write/append/read/clear) |
| `ReloadToolsTool` | `reload_tools` | Hot-reload user tools without server restart |
| `WriteToolTool` | `write_tool` | Write a new user tool file and reload immediately |
| `ListToolsTool` | `list_tools` | Return the live tool list from registry |
| `ToolManualTool` | `tool_manual` | Return manuals/{name}.md or auto-generate from schema |
| `MemorySaveTool` | `memory_save` | Save a fact to long-term memory |
| `MemorySearchTool` | `memory_search` | Search long-term memory facts |
| `MemoryForgetTool` | `memory_forget` | Delete a fact from long-term memory |
| `SpawnAgentTool` | `spawn_agent` | Spawn an isolated subagent (blocking, synchronous from caller's view) |
| `SwitchProfileTool` | `switch_profile` | Switch the active profile for a session |
| `ListProfilesTool` | `list_profiles` | List all available profiles |
| `ShareFileTool` | `share_file` | Copy an existing local file into session files and return a download link |
| `ContentPublishTool` | `content_publish` | Register an existing session file for inline viewing in chat |
| `Model3DTool` | `model_3d` | Compile an OpenSCAD script into a binary STL file |
| `ScadLintTool` | `scad_lint` | Lightweight OpenSCAD source linting before STL compilation |
| `Render3DTool` | `render_3d` | Render preview PNG images from an STL file (up to 3 views) |
| `DeleteToolTool` | `delete_tool` | Delete a user tool file |
| `TestToolTool` | `test_tool` | Run a user tool and verify its output |
| `ReflectTool` | `reflect` | Self-reflection and analysis |

### User tools (`tools/*.py`)

Written by the agent via `write_tool` or manually. Auto-discovered at startup.

- Files starting with `_` are ignored.
- `tools/enabled.json` — list of user tool names to include in all profiles automatically.
- `tools/_template.py` — canonical format reference (not loaded).

Currently present: `get_current_datetime.py`, `gmail.py`, `weather.py`.

---

## Tool formats

### Module-level format (preferred for user tools)

```python
name = "my_tool"
description = "What it does and when to use it — be specific."
parameters = {
    "type": "object",
    "properties": {
        "param": {"type": "string", "description": "..."}
    },
    "required": ["param"]
}

async def execute(params: dict) -> str:
    # Return a plain string on success.
    # Raise an exception to signal failure.
    return "result"
```

No classes, no module-level `print()`. The loader wraps `execute` in a `Tool` subclass automatically.

### Class-based format (built-in tools)

```python
from navi.tools.base import Tool, ToolResult

class MyTool(Tool):
    name = "my_tool"
    description = "..."
    parameters = {"type": "object", "properties": {...}, "required": [...]}

    async def execute(self, params: dict) -> ToolResult:
        return ToolResult(success=True, output="result")
```

`ToolResult` fields:
- `success: bool`
- `output: str` — always a string; LLM sees this
- `error: str | None` — included in output on failure via `to_message_content()`
- `metadata: dict` — internal hints (e.g. `is_image: True` → triggers image injection into context)

---

## Tool loading (`navi/tools/loader.py`)

`load_tools_from_dir(tools_dir)` returns `LoadResult(loaded, errors)`.

Load order:
1. Try module-level format (checks for `name`, `description`, `parameters`, `execute`).
2. Fall back to class-based (scans for `Tool` subclasses).

Errors are **isolated per file** — one broken file does not prevent others from loading. Errors are logged and returned in `LoadResult.errors`.

---

## Hot-reload

`reload_tools` tool calls `ToolRegistry.reload_user_tools(tools_dir)`:
1. Drops all tools that are NOT in `_builtin_names`.
2. Re-runs `load_tools_from_dir`.
3. New tools registered without server restart.

New tools become available from the **next** user message (tool schemas are built at `run_stream()` entry, not during execution).

---

## Self-extension via `write_tool`

`WriteToolTool` validates the code before writing (checks for the 4 required definitions). On success:
1. Writes the file to `tools/{name}.py`.
2. Adds the name to `tools/enabled.json`.
3. Calls `reload_user_tools()` — tool is registered immediately.

The agent should call `tool_manual("write_tool")` before using `write_tool` for the first time — the manual at `manuals/write_tool.md` has the full format reference and a complete example.

---

## Scratchpad and Todo

Both are per-session, stored in-memory keyed by `current_session_id`.

**Scratchpad** — named sections for working notes within a task. Operations: `write`, `append`, `read`, `clear`. Subagents get isolated scratchpads (unique UUID-based session ID in `run_ephemeral()`).

**Todo** — checklist for tracking multi-step plans. Operations: `set` (replace all tasks), `update` (set status of one task), `read`. Statuses: `pending`, `in_progress`, `done`, `failed`, `skipped`.

---

## Image tool flow

When `image_view` succeeds, it returns `ToolResult` with `metadata={"is_image": True, "base64": "..."}`.

The agent detects this and appends a synthetic user message with the image to `session.context` (but not `session.messages`). This makes the image visible to the next LLM call without polluting the display history.

See [`sessions.md`](sessions.md) for the dual-buffer design.
