Roo Code Native Protocol Tool Calling vs Legacy Format: Key Differences Explained

Roo Code's native protocol tool calling system uses strongly-typed nativeArgs with required tool IDs, while the legacy format relies on loose XML or JSON params that lack type safety and are actively being phased out.

Roo Code (formerly Roo Cline) is an AI-powered coding assistant in the RooCodeInc/Roo-Code repository that orchestrates LLM-driven tools through a structured execution framework. The architecture supports two distinct calling protocols that currently exist side-by-side for backward compatibility. Understanding the distinction between the modern native protocol tool calling approach and the deprecated legacy format is essential for extending the tool ecosystem or debugging integration failures.

Core Architectural Differences

The fundamental split between these protocols centers on how tool arguments are structured, validated, and processed by the execution engine.

Argument Structure and Type Safety

Native protocol calls require typed arguments defined in the NativeToolArgs interface located in src/shared/tools.ts. These arguments are passed directly to the tool's execute method as a strongly-typed object. For example, a read_file call uses ReadFileToolParams with explicit path, mode, offset, and limit fields.

Legacy format calls use free-form params containing either raw JSON or stringified XML snippets. The tool must manually inspect this params object and parse values, leading to potential runtime errors from mismatched types or missing fields.

Invocation Requirements

Native protocol blocks must contain an id (the API-generated tool ID) and the nativeArgs property to be recognized. According to src/core/tools/BaseTool.ts, if block.nativeArgs is missing, the system throws a clear error stating that XML tool calls are no longer supported.

Legacy blocks require neither an id nor nativeArgs. They historically relied on XML markup like <read_file>...</read_file> embedded in responses, though this path now triggers immediate errors forcing migration to the native format.

Streaming and Partial Updates

The native protocol supports streaming tool calls through the handlePartial() method, which can safely rely on typed nativeArgs values (like incremental path or offset updates) to render UI progress. Legacy partial handling had to infer values from loosely-typed params, making real-time updates fragile and error-prone.

Implementation in the Codebase

The protocol distinction is enforced across three critical files in the Roo Code source tree.

Typed Definitions in tools.ts

The NativeToolArgs mapping in src/shared/tools.ts (lines 88-99) defines the contract for all native tool calls:

// src/shared/tools.ts
export interface NativeToolArgs {
  read_file: ReadFileToolParams;
  write_to_file: WriteToFileToolParams;
  // ... additional tools
}

This interface ensures that every native call adheres to a predefined schema, enabling compile-time checks and IDE autocomplete for developers extending the system.

Dispatch Logic in BaseTool.ts

The BaseTool.handle() method in src/core/tools/BaseTool.ts (lines 25-48) serves as the central dispatcher that enforces the protocol distinction:

// src/core/tools/BaseTool.ts - lines 44-48
if (!block.nativeArgs) {
  throw new Error("XML tool calls are no longer supported")
}

If nativeArgs is present, the method extracts it directly and passes the typed object to the tool's implementation. If missing, it immediately aborts with a HandleError result, preventing ambiguous legacy calls from executing.

Legacy Detection in NativeToolCallParser.ts

The NativeToolCallParser.createPartialToolUse() method in src/core/assistant-message/NativeToolCallParser.ts (lines 94-106) specifically looks for the legacy read_file "files" envelope to set telemetry flags:

// src/core/assistant-message/NativeToolCallParser.ts - lines 98-105
if (params.files) {
  // Detect legacy read_file format for telemetry
  toolUse.usedLegacyFormat = true
}

This detection allows the system to track migration progress without breaking existing integrations that still use the old envelope format.

Code Examples: Native vs Legacy

{
  "type": "tool_use",
  "id": "tool-123",
  "name": "read_file",
  "nativeArgs": {
    "path": "src/main.ts",
    "mode": "auto",
    "offset": 0,
    "limit": 200
  },
  "partial": false
}

The LLM sends this block, the parser stores the typed nativeArgs, and ReadFileTool.execute() receives a typed object matching ReadFileToolParams. The id field is mandatory for native calls.

Legacy Format Call (Backward Compatibility)

{
  "type": "tool_use",
  "name": "read_file",
  "params": {
    "files": "[{\"path\":\"src/main.ts\",\"mode\":\"auto\"}]"
  },
  "partial": false
}

This triggers the usedLegacyFormat flag in telemetry logs. The parser attempts to extract the old files array and convert it internally to the native shape. If the block omitted files and used plain XML markup instead, BaseTool.handle() would throw an error terminating the execution.

Partial Streaming (Native Only)

{
  "type": "tool_use",
  "id": "tool-456",
  "name": "read_file",
  "partial": true,
  "nativeArgs": {
    "path": "src/large.log",
    "offset": 1024
  }
}

During streaming, the UI can safely reference nativeArgs.path and nativeArgs.offset to show progress, as the type system guarantees these fields exist and contain valid types.

Error Handling and Enforcement

The native protocol provides strict failure modes that prevent silent errors. When nativeArgs are missing, BaseTool.handle() sends a single error tool result and aborts the entire operation. Conversely, legacy calls could continue with ambiguous data, leading to hidden failures or incorrect tool execution.

Additionally, src/core/assistant-message/presentAssistantMessage.ts (lines 112-120) enforces that every native tool use must receive a corresponding tool_result, preventing the "missing result" bug that existed in legacy handling where partially processed XML could leave operations dangling.

Summary

  • Native protocol uses strongly-typed nativeArgs defined in NativeToolArgs and requires a unique id for every tool call.
  • Legacy format relies on loose params objects and XML markup, which now trigger explicit errors rather than executing ambiguously.
  • The system detects remaining legacy usage via the usedLegacyFormat flag when the old read_file "files" envelope is encountered.
  • All new tools must be added to src/shared/tools.ts first, with legacy support only maintained as a temporary migration shim.
  • Streaming and partial updates are only reliable under the native protocol due to type-safe argument access.

Frequently Asked Questions

What happens if an LLM returns XML markup instead of nativeArgs?

Roo Code will throw an explicit error and abort the tool execution. According to src/core/tools/BaseTool.ts (lines 44-48), if block.nativeArgs is missing and the system detects XML-style markup or legacy params, it returns a HandleError result with the message "XML tool calls are no longer supported." This forces immediate migration to the typed native protocol.

How does Roo Code track which conversations still use legacy formats?

The system sets a usedLegacyFormat telemetry flag when it detects specific legacy patterns. In src/core/assistant-message/NativeToolCallParser.ts (lines 98-105), the parser checks for the presence of a files field in the parameters—specifically the old read_file envelope format—and sets toolUse.usedLegacyFormat = true. This allows the development team to monitor adoption rates without breaking existing functionality.

Can I temporarily use legacy params for custom tools during development?

No, the legacy code path is deprecated and slated for removal. While the system currently maintains backward compatibility for the specific read_file "files" envelope (converting it internally to native format), all other XML and loose params usage triggers errors. New tools must be defined in the NativeToolArgs interface in src/shared/tools.ts and use the nativeArgs property exclusively.

Does migrating to the native protocol improve tool execution performance?

Yes, primarily through reduced parsing overhead and safer partial execution. Native calls pass typed objects directly to execute() methods without manual string parsing or type coercion. Additionally, streaming tools can access incremental nativeArgs updates through handlePartial() without defensive coding against missing or mistyped fields, resulting in more responsive UI updates and fewer runtime exceptions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →