-
-
Notifications
You must be signed in to change notification settings - Fork 0
Tools System
M31A provides a curated set of tools that the LLM can invoke to interact with the filesystem, run commands, search code, and access the web. Tools are registered on a Dispatcher that handles permissions, rate limiting, and execution.
LLM Response (tool_call chunks)
│
▼
workflow/engine.go → consumeStreamWithTools()
│
▼
tools/dispatcher.go → Execute(ToolCall)
├── Rate limiter (token bucket)
├── Permission check (rules + risk level)
├── Tool lookup (by name)
└── Tool.Execute(ctx, input)
│
▼
ToolResult → fed back to LLM
Source: internal/tools/bash.go
| Property | Value |
|---|---|
| Risk Level | dangerous |
| Timeout | 30 minutes (configurable per call, max 1800s) |
| Output Limit | 50,000 characters |
| Schema | {command: string, timeout?: integer} |
Features:
- Process group management (SIGINT then SIGKILL after grace period)
- Non-interactive environment variables (
CI=true,DEBIAN_FRONTEND=noninteractive) - Stdin closed immediately to prevent hangs
- Binary output detection (null byte check)
- Output truncation with
[... output truncated]marker - Concurrent stdout/stderr reading with per-stream buffers
Source: internal/tools/fileread.go
| Property | Value |
|---|---|
| Risk Level | safe |
| Max File Size | 5 MB |
Features:
- Line offset and limit for partial reads
- Line number prefix (
cat -nformat) - Long line truncation (>2000 chars)
- Support for images and PDFs (rendered as attachments)
Source: internal/tools/filewrite.go
| Property | Value |
|---|---|
| Risk Level | medium |
Features:
- Atomic writes (temp file + rename)
- Auto-backup before overwrite (configurable, max 10 backups per file)
- Directory auto-creation for new file paths
Source: internal/tools/edit.go
| Property | Value |
|---|---|
| Risk Level | medium |
Features:
- Exact string replacement (old_string → new_string)
- Conflict detection when multiple matches exist
-
allow_multipleflag for batch replacements - Instruction-based editing (semantic description of the change)
- Same atomic write and backup as FileWrite
Source: internal/tools/glob.go
| Property | Value |
|---|---|
| Risk Level | safe |
| Max Results | 1,000 (configurable) |
Features:
- Standard glob patterns (
**/*.go,src/**/*.ts) - Fallback retry with
*.<ext>pattern when no matches - Sorted results by modification time
- Skip directories:
node_modules,vendor,.next,dist,build,target,.venv,__pycache__
Source: internal/tools/grep.go
| Property | Value |
|---|---|
| Risk Level | safe |
| Max Results | 100 (configurable) |
Features:
- Full regex support via ripgrep-like semantics
- File type filtering (
includeparameter) - Context lines (before/after)
- Output modes:
content,files_with_matches,count - Multiline matching support
- Result truncation with count reporting
Source: internal/tools/filedelete.go
| Property | Value |
|---|---|
| Risk Level | destructive |
Features:
- Auto-backup before deletion
- Path validation (prevents traversal attacks)
Source: internal/tools/filemove.go
| Property | Value |
|---|---|
| Risk Level | medium |
Source: internal/tools/filelist.go
| Property | Value |
|---|---|
| Risk Level | safe |
Source: internal/tools/codemap.go
| Property | Value |
|---|---|
| Risk Level | safe |
Features:
- Project structure mapping
- Symbol extraction
- Dependency graph generation
Source: internal/tools/webfetch.go
| Property | Value |
|---|---|
| Risk Level | medium |
| Max Redirects | 5 (configurable) |
Features:
- HTTP to HTTPS auto-upgrade
- HTML to markdown conversion
- SSRF protection (blocks private, loopback, link-local IPs)
- Custom User-Agent header
- Content type detection
Source: internal/tools/websearch.go
| Property | Value |
|---|---|
| Risk Level | safe |
Features:
- SearXNG meta-search engine backend
- No API key required
- Configurable instance URL
- Structured results (title, URL, snippet)
Source: internal/tools/todo.go
| Property | Value |
|---|---|
| Risk Level | safe |
Features:
- Task tracking within the session
- Stored in session directory
Source: internal/tools/question.go
| Property | Value |
|---|---|
| Risk Level | safe |
Features:
- Structured questions with multiple-choice options
- Per-request response routing (prevents cross-caller mix-ups)
- Timeout support
Source: internal/tools/agent.go
| Property | Value |
|---|---|
| Risk Level | medium |
Features:
- Spawns parallel subagents with isolated worktrees
- Background execution support
- See Subagents for details
All tools are registered in internal/tools/defaults.go:
func DefaultDispatcher(workDir, backupDir, sessionsDir string, cfg *PermissionsConfig) (*Dispatcher, error) {
d := NewDispatcher(cfg)
d.Register(NewBash(workDir))
d.Register(NewFileRead(workDir))
d.Register(NewFileWrite(workDir, backupDir))
d.Register(NewEdit(workDir, backupDir))
d.Register(NewTodoWrite(sessionsDir, ""))
d.Register(NewWebFetch(sessionsDir, false))
d.Register(NewWebSearch(""))
d.Register(NewAskUserQuestion(...))
d.Register(NewGlob(workDir))
d.Register(NewGrep(workDir))
d.Register(NewFileList(workDir))
d.Register(NewFileDelete(workDir, backupDir))
d.Register(NewFileMove(workDir))
d.Register(NewCodeMap(workDir))
// Agent tool registered separately on parent dispatcher
return d, nil
}Source: internal/tools/dispatcher.go
The Dispatcher is the central hub for tool execution. It provides:
Token bucket algorithm prevents resource exhaustion from LLMs generating excessive tool calls:
- Burst capacity:
ToolRateLimitBurst - Refill rate:
ToolRateLimitPerSec - Background goroutine refills tokens; stopped via
Stop()withsync.Oncesafety
Every tool execution passes through ensurePermission():
- Rule matching -- Checks tool name and arguments against configured rules
-
Risk assessment -- Classifies as
safe,medium,dangerous, ordestructive - Agent defaults -- Per-agent permission profiles
- Interactive prompt -- Modal in the TUI (allow / allow always / deny / exit)
- Non-interactive fallback -- Blocks dangerous tools in shell mode
The dispatcher handles two input formats from LLMs:
-
Nested:
{"name":"Bash", "params":{"command":"ls"}} -
Direct:
{"name":"Bash", "command":"ls"}
Both are normalized to the same internal ToolInput struct.
Input is capped at 1000 parameters per tool call to prevent abuse.
Communication between the dispatcher and TUI uses channels:
Dispatcher ──(PermissionRequest)──→ TUI modal
Dispatcher ←──(PermissionResponse)── TUI user action
Each request has a monotonically increasing ID (atomic counter) for routing responses to the correct waiter. Per-request channels prevent cross-caller response mix-ups in concurrent scenarios.