JuliusBrussee/caveman · warning · MiddlewareError

invalid_recovery_arguments

invalid_recovery_arguments

Error message

invalid_recovery_arguments

What it means

call_tool executes a recovery action on the middleware binding using arguments supplied by the agent through the recovery tool schema. If the argument dict contains keys outside RECOVERY_SCHEMA['properties'] or lacks the required 'handle' key, the middleware raises MiddlewareError('invalid_recovery_arguments'); the handler converts it into a ToolResult with is_error=True rather than crashing the agent loop.

Solutions

  1. Inspect the returned ToolResult's error JSON and re-prompt the model to call the tool with only schema-defined parameters plus a valid 'handle'.
  2. Ensure the agent is bound to the RECOVERY_SCHEMA (set via _schema()); mis-bound or custom schemas cause key mismatches.
  3. Refresh the recovery handle by triggering a new recovery session; old handles may be invalid after middleware restart.
  4. In tests, validate your args dict against RECOVERY_SCHEMA['properties'] before calling call_tool.

Example fix

// before
await workbench.call_tool("caveman_recovery", {"action": "retry"})  # missing handle
// after
await workbench.call_tool("caveman_recovery", {"handle": handle_id, "action": "retry"})
Defensive patterns

Strategy: validation

Validate before calling

def valid_recovery_args(args) -> bool:
    keys = set(args or {})
    return "handle" in keys and keys <= set(RECOVERY_SCHEMA["properties"])

Try / catch

result = await workbench.call_tool("caveman_recovery", args)
if result.is_error:
    err = json.loads(result.result[0].content).get("error", {})
    if err.get("code") == "invalid_recovery_arguments":
        # re-prompt model or refresh handle and retry with schema-valid args
        retry_args = {k: v for k, v in args.items() if k in RECOVERY_SCHEMA["properties"]} | {"handle": fresh_handle}
        result = await workbench.call_tool("caveman_recovery", retry_args)

Prevention

When it happens

Trigger: An LLM hallucinating extra/unknown parameters for the recovery tool; omitting the required 'handle' field; supplying malformed values that make binding.execute raise TypeError/ValueError; calling call_tool directly with hand-built arguments missing 'handle'.

Common situations: Small/weak models inventing parameters not in the JSON schema; stale recovery handles after a restart; agents reusing recovery args from a previous turn; hand-written tool calls in tests.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/50f262184631935b. Report an issue: GitHub.

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/autogen.py:412

        return [*tools, self.recovery_schema] if self.recovery_enabled else tools

    async def call_tool(self, name, arguments=None, cancellation_token=None, call_id=None):
        # Refresh dynamic registries before deciding which implementation owns
        # the name. A collision always belongs to the original workbench.
        await self.list_tools()
        if name != "caveman_retrieve" or not self.recovery_enabled:
            for workbench in self.delegates:
                if any(tool["name"] == name for tool in await workbench.list_tools()):
                    return await workbench.call_tool(name, arguments, cancellation_token, call_id)
            if self.delegates:
                return await self.delegates[0].call_tool(name, arguments, cancellation_token, call_id)
            return ToolResult(name=name, result=[TextResultContent(content=f"Tool {name} not found.")], is_error=True)
        cancellation = _Cancellation(cancellation_token)
        cancellation.check()
        try:
            args = dict(arguments or {})
            if set(args) - set(RECOVERY_SCHEMA["properties"]) or "handle" not in args:
                raise MiddlewareError("invalid_recovery_arguments")
            result = await cancellation.wait(self.binding.execute(args))
            return ToolResult(name=name, result=[TextResultContent(content=json.dumps(result, ensure_ascii=False, separators=(",", ":")))])
        except MiddlewareError as error:
            return ToolResult(name=name, result=[TextResultContent(content=json.dumps({"error": {"code": error.code}}))], is_error=True)
        except (TypeError, ValueError):
            return ToolResult(name=name, result=[TextResultContent(content='{"error":{"code":"invalid_recovery_arguments"}}')], is_error=True)

    async def call_tool_stream(self, name, arguments=None, cancellation_token=None, call_id=None):
        await self.list_tools()
        workbench = None
        if name != "caveman_retrieve" or not self.recovery_enabled:
            for candidate in self.delegates:
                if any(tool["name"] == name for tool in await candidate.list_tools()):
                    workbench = candidate
                    break
        if not isinstance(workbench, StaticStreamWorkbench):
            yield await self.call_tool(name, arguments, cancellation_token, call_id)
            return

View on GitHub (pinned to 3ee70a1026)