-
-
Notifications
You must be signed in to change notification settings - Fork 0
Tools System
M31 Autonomous (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.
flowchart TD
LLM[LLM Response<br/>tool_call chunks] --> Engine[workflow/engine.go<br/>consumeStreamWithTools]
Engine --> Dispatcher[tools/dispatcher.go<br/>Execute ToolCall]
Dispatcher --> Rate[Rate Limiter<br/>token bucket]
Dispatcher --> Perm[Permission Check<br/>rules + risk level]
Dispatcher --> Lookup[Tool Lookup<br/>by name]
Lookup --> Exec[Tool.Execute<br/>ctx, input]
Exec --> Result[ToolResult]
Result --> LLM
style LLM fill:#e1f5fe
style Dispatcher fill:#fff3e0
style Result fill:#e8f5e9
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 | dangerous |
Features:
- Exact string replacement (old_string → new_string)
- Line-range replacement (start_line + end_line + new_string)
- Cascading replace: 7 strategies (exact, line-trimmed, whitespace-normalized, indent-normalized, line-skip, fuzzy-anchor)
- Levenshtein similarity for fuzzy matching
-
replace_allflag for batch replacements - LCS-based diff summary generation
- 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) |
| Pattern Length | Capped to prevent ReDoS |
Features:
- Ripgrep backend when available, pure-Go fallback otherwise
- Full regex support with
fixed_stringmode for literal searches - File type filtering (
includeglob parameter) - Context lines (before/after match)
- Ring buffer streaming mode: O(contextLines) memory when context > 0, O(1) when context = 0
- ReDoS protection: rejects nested/adjacent quantifier patterns
- Gitignore-aware file filtering with mtime-based cache
- 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
Source: internal/tools/codecomplexity.go
| Property | Value |
|---|---|
| Risk Level | safe |
| Schema | {skip_dirs?: string[]} |
Features:
- Scans Go source files in the project directory
- Counts files and lines per package and per file
- Ranks top packages and files by size
- Produces a complexity score (
low,medium,high) - Respects skip directories (configurable, merges with defaults)
See CodeComplexity Tool for detailed usage.
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.