Part III — The tool system

This part covers Chapters 9–13: an overview of the tool system first, then the smart-coding and security-analysis tools in turn, followed by how external tool services are connected, and the permission and approval mechanism that runs through every tool.


9. Tools overview

A tool is what lets the AI "do something" on your computer.

9.1 Three categories

  • Smart coding tools: read / write files, run shell, version control, find, etc. (Chapter 10).
  • Security analysis tools: hash, strings, IOC, executable parsing, disassembly, rule matching, traffic metadata (Chapter 11).
  • External tool services: tools from external systems like Notion, internal APIs, private knowledge bases (Chapter 12).

9.2 Tool switches

Each tool can be set to:

  • Allow: pass through.
  • Ask: pop an approval modal on every call.
  • Deny: reject directly.

Entry: the gear next to the mode slider in the input area (agent manager) → the "Configure tools" tab, per work mode. The three states actually constrain the run: "Deny" takes effect on both the main agent and subagents; "Ask" is wired into the real confirmation flow, pausing the call until you approve; uninstalling a plugin or external tool service also cleans up leftover tool config, keeping on-disk config in line with the tools actually available.

A steadier tool list: if fetching the tool list fails, it retries with backoff and only alerts after repeated failures; the fallback tool table is generated from the same source as the built-in tools, so the list no longer comes up mysteriously short.

9.2.1 Tool category lazy loading (on by default)

There are now well over a hundred tools; sending every tool description to the model on every turn would both eat context and slow responses. So only a commonly-used subset (a couple of dozen) is carried by default, and the rest are loaded by category when they're needed.

  • Always-resident categories: file read/write and search, command execution, tool introspection, subtask delegation and todo planning — needed on almost every turn.
  • Categories loaded on demand (16): code intelligence, git, security analysis, threat intelligence, SBOM, external tool services, web search, skills, tests, lint, SAST, plugin marketplace, plugin management, GitHub, system info, time.
  • Two ways to load: relevant words in your question preload the matching categories automatically; the assistant can also call tools.load to pull in a category by name. External tool services connected mid-conversation are admitted automatically.
  • The assistant knows the list is incomplete: the system prompt says so and includes the category directory, so the assistant won't conclude a capability doesn't exist just because it isn't listed; when it queries its own available tools, the result also notes that "not visible ≠ not there — load the category first".

The switch: agent manager → the "Configure tools" tab → "Tool category lazy loading". Turn it off and every tool stays resident. When there aren't many tools available anyway (32 or fewer), the whole table is admitted directly and no lazy loading happens.

If you use a particular external tool service often, you can turn lazy loading off for just that service in the same panel so its tools are always available — see §12.4.

9.3 Tool namespaces

External-service tools are auto-prefixed (e.g. notion.*) to coexist with built-in tools cleanly.

9.4 Tool-call visibility

Each tool call appears as a collapsible card in the message stream containing:

  • Tool name + parameter summary
  • Elapsed time
  • Result (success / failure / truncated)

You can inspect each call independently for audit and traceability.

Inline progress for in-flight calls: long-running tools show live progress to the right of the tool name on the call row (a spinner icon + text, plus a thin progress bar when there's a ratio); the progress text takes priority and the parameter summary gives way and is truncated. Only calls that are genuinely in flight light up. The progress texts are currently produced in Chinese; the literals are quoted below with an English gloss.

Source What is shown
sec.decompile / code.ask waiting for model output (a heartbeat every 20 seconds) "等待 {模型} 输出(已 Ns)" (waiting for {model} output, N s so far) → "{模型} 思考中(已 Ns,思考 ~N tokens)" ({model} thinking, N s so far, ~N reasoning tokens) → "{模型} 生成中(约 P%,预计还需 ~Ms,已 Ns,~N tokens,R tok/s)" ({model} generating, about P %, ~M s remaining, N s so far, ~N tokens, R tok/s); percentage and estimated time remaining come first and are estimated from the history of the same tool / model — the first call reports no percentage, and it shows at most 99%
Building the code index "建立代码索引 X/N" (building code index X/N), with a ratio bar
Language-server auto-download, SBOM sync Text progress
Any other tool running longer than 60 seconds "已运行 1m05s" (running for 1m05s), ticking every second; when the tool declares an execution time limit the cap is shown too, e.g. "已运行 1m05s / 上限 2m00s" (running 1m05s / limit 2m00s) — fs.exec defaults to 120 seconds, test.run 300, lint.run 180 and sast.run 240, adjustable via call parameters or policy. Time spent parsing arguments, waiting for approval or on hooks doesn't count; not shown while the tool is reporting its own progress
  • Subtask dispatch and waiting tools (Agent / AgentParallel / TaskWait, etc.) and questions to you don't show the running timer.
  • Progress in background sessions is reported in a notice that updates in place ("session name: progress"); the running timer is only shown on the call row.
  • In "Compact" density, the call currently showing inline progress isn't merged into "N tool call attempts".

9.4.1 Hook execution visibility

Each hook execution now appears in the conversation stream as a system message, rendered as a collapsible block (Markdown content) similar to a tool call. You can see whether a hook ran, what it produced, and whether it affected the next step — no more black box.

9.4.1.1 Tool-execution idle timeout (default 10 min)

A safety net was added to tool execution: if a tool gives no feedback at all for an extended time, it's wound up safely instead of leaving the whole turn hanging. This safety net is judged on idleness (no feedback), not total duration — as long as a tool keeps reporting progress, a long task is never cut off by a fixed time limit.

  • Trigger: the tool reports no progress for ≥ 10 minutes by default. What counts is progress reported by the tool itself; keep-alive heartbeats at the gateway layer are not feedback.
  • Doesn't override the tool's own time limit: for tools that declare an execution time limit (such as fs.exec's timeout), the safety net waits at least until "tool time limit + 6 minutes" before acting — e.g. a long command with fs.exec timeout=900 is no longer abandoned at the 10-minute mark.
  • External tool-service (MCP) calls are now idle-based too: a call only times out after 35 minutes without tool feedback (each piece of feedback restarts the clock), instead of cancelling a call that is still giving feedback at 35 minutes total; when a time limit has been announced it waits at least "remaining time limit + 5 minutes".
  • Late progress from an abandoned call is never attributed to the next call of the same name.
  • Exemption: the sub-Agent family (Agent / AgentParallel / TaskWait / TaskOutput / TaskStop) uses its own idle window + absolute ceiling (see §15.2.1), and is not killed by this idle timeout by mistake.

9.4.1.2 Repeated-call notice (suspected spinning)

The assistant occasionally falls into a loop of calling the same tool with the same arguments over and over — it keeps moving, so the idle timeout never catches it, yet nothing is actually progressing while your quota keeps draining.

  • After 3 consecutive identical calls to the same tool, a note in the tool result asks the assistant to try a different approach; the wording escalates as the count climbs.
  • The session is marked "suspected spinning" in the session list, with the repeat count and tool name on hover; both the sidebar and the top-tabs layout show the mark. A notification is sent as well, so you notice even when you've switched to another session.
  • The mark survives an application restart, so it isn't lost on the way; it clears automatically once you send a new message in that session.
  • Polling tools (waiting on a subtask, pulling command output, and the like) inherently need repeated calls and are excluded.

9.4.1.3 Reply repetition detection (repetition spin)

Unlike the "same tool, same arguments, over and over" case above, this covers no tool calls, just similar content repeated in the body text — the model flooding one line within a single reply, or answering with the same sentence several turns in a row without doing anything.

  • Line-level cut-off within a turn: when the same line appears ≥ 3 times within the reply's last 25 lines (at least twice adjacently), or ≥ 5 times, the output is cut off on the spot — the body is kept up to the first pass through the repeated part, and reasoning already streamed is kept. Rotation among a few sentences and floods of very short lines such as "OK." or "Executing." are recognised too; code blocks, table rows and punctuation-only lines don't count. Replies that already carry tool calls are not cut.
  • Cross-turn reminder and halt: when several consecutive turns call no tools and reply with the same content, the model gets a reminder from the 3rd time (not persisted), stronger at the 5th; at the 8th (or after 3 accumulated single-turn cut-offs along one chain) it halts automatically, also pausing automatic continuation, and leaves a note in the session: "模型最近 N 次回复都是「…」且没有任何实际动作,判定为复读空转,已自动停止……" (the model's last N replies were all "…" with no actual action — judged a repetition spin and stopped automatically). Add more specific instructions and continue, or try another model. Your new input and tool results both reset the count.
  • Visible: the session list also shows the "Possible spin" mark, and background sessions raise a notification; on halt you're notified even for the current session.

9.4.2 Trace panel

The stats bubble at the top of the session (StatsPopover) has a Trace panel next to the Stats view: it lays out which tools the AI called in one run, in what order, with each call's inputs / outputs and duration — a timeline view that makes it easy to see how the AI walked through the work, and to spot "where did it go off the rails / where is the time being spent".

Split by run, not one mush per session: a row of run selectors at the top of the panel splits the session into one run per message you sent, defaulting to the most recent one; each run chip carries its step count, tool calls, tokens and failures, so you can pick out the suspicious one at a glance.

The summary card shows, for this run:

Metric Notes
Turns / tool calls / tool failures / errors The basics for this run
Gate injections Total automatic injections; hover for the breakdown by source (gate / goal / compaction / todos / self-heal)
Total tokens The semantics are stated on the card: it includes steps already excluded from the context window (so it can exceed "current context usage")
Cache hit Prompt-cache hit ratio — did the repeated prefix actually save anything
Suspected loop The highest repeat count for one "tool + args" fingerprint, and the tool's name; flagged from 3 repeats up (see §9.4.1.2)
Compactions How many SUMMARY compactions happened in this run
Todo paused How many times the model reported it had stopped advancing todos as you asked (see §16)
Tool time / wall clock / models used Where the time went, and which models were involved

Below it there is a per-tool stats table (call count / failures / P50 / P95 / max duration) and a "Prompt per turn" trend strip — prompt tokens climbing steadily turn after turn is the classic fingerprint of "stuck in a loop / context is ballooning", and it's far quicker to read than stepping through.

The timeline:

  • Each step shows sequence / role / kind / source / tool name, duration, prompt / completion / total tokens, model, finish reason, success and error message, plus a preview of both the content and the call arguments (for tool steps you can see exactly what was passed without going back to the message stream).
  • Click a step to jump to the matching spot in the message stream.
  • Filter: All / Tools only / Errors only — go straight to "Errors only" when troubleshooting.
  • Long runs only load their tail (last 500 steps by default; the aggregates are still over the whole run), with "N earlier steps omitted" at the top and a "Load earlier" button to page back — an enormous run is never poured in all at once.
  • Special steps are called out: "Context compaction (SUMMARY)" states how many records it absorbed; "Todo paused" marks the point where the model signalled it was stopping (that marker never appears in the body text — it is only visible here).

Subagent drill-down: dispatch steps such as Agent / AgentParallel carry a "Sub-trace" entry — go in to see that subagent's own full trace, then click "Back to main trace". While drilled down, steps don't offer the jump (those lines belong to the sub-session file and have no counterpart in the main message stream).

Export: the Export button at the top right writes the current view (current run / current sub-trace) to a file, with a "Redact" checkbox next to it that is ticked by default — when ticked the rules from Settings → Data masking are applied before writing (no rules configured means no change); untick to export verbatim. On success the toast gives you the output path. Export and redaction are not offered inside a sub-trace view.

Refresh and error state: the data comes from server-side aggregation over the session JSONL, loaded once when you open the panel, with no live polling; the panel refreshes itself once when the current run finishes (the JSONL now has the terminal records), and you can always hit the refresh button at the top right. On a read failure the panel says "Failed to read the trace — click Refresh to retry" instead of going blank.

9.5 Let the assistant configure MCP servers itself

In addition to manually adding external services from the Settings panel, the assistant can configure MCP servers for you mid-conversation. AVL Code provides 9 mcp.* tools, typically used together with the built-in mcp-admin agent:

Tool Purpose
mcp.list_servers List all MCP servers
mcp.describe_server Detail one server (secrets masked)
mcp.list_tools Tools a server exposes
mcp.test_server Real-handshake probe with a temporary connection (30 s budget), distinguishing six stages validate_args / policy_denied / needs_approval / connect / list_tools / ok: policy_denied = the stdio command is forbidden by the current work mode / command policy, or the http / sse address points at this machine / the LAN / link-local (including localhost, *.internal and cloud metadata addresses; not applied when the session is in unrestricted mode — test your own local server with Test in the server form under Settings → MCP); needs_approval = the stdio command is not in the command allow-list, your approval has been requested and the probe retries once you approve
mcp.add_server Add (write, two-step)
mcp.remove_server Remove (write, two-step). The default MCP backend can also be removed now (the gateway used to refuse this, leaving the default backend undeletable)
mcp.toggle_server Enable / disable (write, two-step)
mcp.import_config Bulk import; recognises native format and the Claude Desktop {mcpServers:{...}} shape
mcp.export_config Export the current config (secrets masked)

Two-step (dry-run): write tools (add / remove / toggle / import) require an explicit confirm=true to actually persist; the first call returns a dry-run preview (full command line, routing, default-backend changes, conflicts) for you to review. This is a fallback gate when no PreToolUse defer hook is configured — the LLM can't single-step into a destructive action.

Secret masking: read-side tools only show the last 4 characters of authToken (the rest as asterisks), and environment variable values are fully masked, so credentials never leak into the conversation.

Typical conversation:

"Hook up mcp-trends-hub@1.6.0 for me."

Assistant → calls mcp.add_server (dry-run) → returns preview → you confirm → assistant calls mcp.add_server(confirm=true) → config written → calls mcp.test_server for a real handshake → reports 21 trending tools are available.