How DeepSeek-Reasonix Handles Tool Execution Errors: Error-as-Data Pattern
The Reasonix agent treats tool execution errors as first-class data, capturing them as strings and feeding them back to the LLM to enable autonomous recovery rather than failing the entire turn.
The DeepSeek-Reasonix project implements a resilient agent architecture where tool failures are expected operational conditions rather than catastrophic exceptions. Understanding how the system handles tool execution errors is crucial for developers building robust LLM-powered applications. According to the Reasonix source code, the agent converts every tool error into structured feedback that the model can use to retry, fallback, or abort gracefully.
The Tool Interface and Error Contract
Every capability available to the LLM implements the Tool interface defined in internal/tool/tool.go. This contract establishes how the runtime communicates failures back to the orchestration layer:
type Tool interface {
Name() string
Description() string
Schema() json.RawMessage
// Execute parses the model‑generated raw JSON args and returns result text
// to feed back to the model.
Execute(ctx context.Context, args json.RawMessage) (string, error)
ReadOnly() bool
}
The Execute method signature is critical: it returns both a result string and an error. When Execute returns a non-nil error, the agent does not panic or terminate. Instead, the orchestration layer in internal/control/controller.go intercepts the error and treats it as a valid tool response that requires special handling.
The Four-Stage Error Handling Pipeline
When a tool execution fails, the Reasonix agent processes the error through a consistent pipeline:
- Capture the error - The runtime extracts the error object from the
Executereturn value. - Convert to string - The error's
Error()value becomes the tool's output text. - Feed back to LLM - This string is presented to the model as the tool's result, allowing the LLM to decide whether to retry with different parameters, use a fallback tool, or abort the plan.
- Log for debugging - The failure is recorded via the standard logger for operators to audit, while execution continues for the current turn.
This error-as-data approach ensures that transient failures (network timeouts, missing files, invalid commands) become recoverable states rather than workflow interruptions.
Parallel Execution and Isolated Failures
The agent optimizes performance by running read-only tools in parallel batches. The ReadOnly() method indicates whether a tool modifies state; when true, the runtime can safely invoke multiple tools concurrently.
When executing parallel read-only tools, errors are isolated to individual calls. As implemented in internal/plugin/spec.go, if one tool in a batch fails, other parallel executions continue unaffected. The results array contains a mix of successful outputs and error strings, with each failure scoped only to its specific invocation.
This isolation prevents a single flaky tool from destabilizing the entire reasoning turn, while still providing the LLM with complete information about which specific operations succeeded or failed.
Concrete Implementation Examples
The internal/tool/builtin/bash.go file demonstrates how concrete tools surface execution failures. When running shell commands, any exit errors or process failures are returned as the error value from Execute, following the interface contract.
The test suite in internal/tool/registry_test.go validates this behavior, confirming that errors propagate unchanged from the tool implementation through the registry to the controller. These tests ensure that the error handling pipeline remains intact across different tool implementations.
Practical Error Handling Patterns
Handling Single Tool Failures
When invoking a tool directly, always check the error return and convert it to a string for the LLM:
// Assume we have a registry and a tool name from the model.
tool, ok := reg.Get("bash")
if !ok {
log.Fatalf("tool not found")
}
// Example arguments the model supplied (JSON‑encoded).
args := json.RawMessage(`{"cmd":"ls /nonexistent"}`)
result, err := tool.Execute(context.Background(), args)
if err != nil {
// Convert the error to a string that the LLM can read.
result = fmt.Sprintf("tool error: %v", err)
// Log for operators.
log.Printf("tool %s failed: %v", tool.Name(), err)
}
// Feed `result` back to the model as the tool’s output.
fmt.Println("Tool output:", result)
Parallel Execution with Error Isolation
For read-only tools that can run concurrently, handle errors per goroutine:
var wg sync.WaitGroup
results := make([]string, len(tools))
for i, t := range tools {
wg.Add(1)
go func(i int, t tool.Tool) {
defer wg.Done()
out, err := t.Execute(ctx, args[i])
if err != nil {
out = fmt.Sprintf("error: %v", err)
}
results[i] = out
}(i, t)
}
wg.Wait()
// `results` now contains a mix of successful outputs and error strings.
Write Tool Error Propagation
Writer tools follow the same pattern, ensuring filesystem or state-modification failures are visible to the model:
// write_file implements Tool and returns an error if the filesystem write fails.
out, err := writeFileTool.Execute(ctx, json.RawMessage(`{"path":"tmp/boom.txt","content":"data"}`))
if err != nil {
// The agent will surface the error to the model.
out = fmt.Sprintf("write_file failed: %v", err)
}
Summary
- Tool execution errors are never swallowed in Reasonix; they become structured feedback for the LLM.
- The
Toolinterface ininternal/tool/tool.gomandates thatExecutereturns(string, error), establishing the error-handling contract. - Errors are converted to strings and presented to the model as tool output, enabling autonomous recovery strategies.
- Read-only tools execute in parallel with isolated failure domains, preventing single-tool crashes from aborting the entire batch.
- The orchestration layer in
internal/control/controller.gomanages the error pipeline without terminating the reasoning turn.
Frequently Asked Questions
Does the agent stop execution when a tool fails?
No, the agent treats tool execution errors as data rather than fatal exceptions. Unless the error triggers a panic in a writer-tool that would corrupt the workspace, the agent captures the error string, logs it for debugging, and feeds it back to the LLM as the tool's result. The model then decides whether to retry, use an alternative tool, or abort the plan.
How are errors handled when running multiple tools in parallel?
When executing read-only tools in parallel batches, each tool runs in its own goroutine with isolated error handling. If one tool fails, the error is captured locally for that specific invocation while other parallel executions continue to completion. The results array contains both successful outputs and error messages, allowing the LLM to see exactly which operations failed without losing the successful results.
Can the LLM see the full error details or just a summary?
The LLM receives the complete error string via the Error() method of the Go error interface. For tools like those in internal/tool/builtin/bash.go, this includes command exit codes, stderr output, and filesystem path information. The agent does not truncate or sanitize these details, giving the model maximum context to diagnose and recover from failures.
What is the difference between read-only and write tool error handling?
While both tool types follow the same Execute error signature, read-only tools benefit from parallel execution isolation as specified in internal/plugin/spec.go. Write tools typically execute sequentially to prevent race conditions, but their errors are handled identically—converted to strings and presented to the LLM. The critical distinction is that write-tool panics might trigger higher-level fatal handling if they risk workspace corruption, whereas read-only tool failures are always non-fatal.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →