Skip to content

Self-Healing

Self-healing is one of Anvil’s most powerful features. When a tool fails, Anvil automatically regenerates it with the error context, often fixing the issue without any manual intervention.

┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Execute │────▶│ Failed │────▶│ Capture │
│ Tool │ │ │ │ Context │
└─────────────┘ └─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Retry │◀────│ Regenerate │◀────│ LLM with │
│ Execute │ │ Code │ │ Error │
└─────────────┘ └─────────────┘ └─────────────┘

When tool.run() raises an exception, Anvil catches it:

result = tool.run(query="test")
# RuntimeError: API returned 401 Unauthorized

Anvil collects:

  • The original intent
  • The current (failing) code
  • The full error message and traceback
  • The arguments that caused the failure

The LLM receives a specialized prompt:

The following tool code is failing with this error:
[error message]
Original intent: [intent]
Current code: [code]
Arguments: [args]
Please fix the code to handle this error.

The new code is saved and the tool is re-executed with the same arguments.

Enable or disable self-healing when initializing Anvil:

# Enabled by default
anvil = Anvil(self_healing=True)
# Disable self-healing
anvil = Anvil(self_healing=False)
# Limit healing attempts
anvil = Anvil(self_healing=True, max_heal_attempts=3)
ParameterDefaultDescription
self_healingTrueEnable automatic healing
max_heal_attempts2Maximum regeneration attempts per tool

Anvil tracks healing attempts per tool to prevent infinite loops:

# First failure → Heal attempt 1
# Second failure → Heal attempt 2
# Third failure → Raise exception (max attempts reached)

Reset the counter manually:

# Reset specific tool
anvil.reset_heal_attempts("search_tool")
# Reset all tools
anvil.reset_heal_attempts()

Self-healing works best for:

  • API errors - Authentication issues, endpoint changes
  • Import errors - Missing dependencies
  • Type errors - Wrong argument types
  • Response parsing - Changed API response formats

Imagine an API changes its authentication method:

# Original generated code (v1.0)
def run(query):
response = requests.get(
"https://api.example.com/search",
headers={"X-API-Key": os.environ["API_KEY"]}
)
return response.json()

The API now requires Bearer tokens. When the tool fails with 401 Unauthorized, Anvil regenerates:

# Self-healed code (v1.1)
def run(query):
response = requests.get(
"https://api.example.com/search",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"}
)
return response.json()

Each healing increments the minor version:

1.0 → Initial generation
1.1 → First healing
1.2 → Second healing

View version history:

info = anvil.get_tool_info("search_tool")
print(info["version"]) # "1.2"

Self-healing events are logged for observability:

anvil = Anvil(log_file="anvil.log")
# After a healing event
events = anvil.logger.get_history(event_type="tool_healed")
for event in events:
print(f"{event.tool_name}: healed at {event.timestamp}")

Self-healing has limitations:

  1. Logic errors - The LLM may repeat the same mistake
  2. Missing credentials - Can’t fix missing API keys
  3. External service down - Network issues aren’t code problems
  4. Complex state - Issues requiring broader context

For these cases, the error is raised after max attempts.

For critical tools, you might want manual control:

# Disable self-healing globally
anvil = Anvil(self_healing=False)
# Or use run_safe() to handle errors yourself
result = tool.run_safe(query="test")
if not result.success:
# Handle manually
print(f"Error: {result.error}")