JuliusBrussee/caveman · error · ValueError
AutoGen tools and workbench are mutually exclusive
Error message
AutoGen tools and workbench are mutually exclusive
What it means
with_caveman_agent wraps an AutoGen agent's options with the Caveman middleware. AutoGen supports passing tools either as a `tools` list or as a `workbench`, but not both simultaneously. The library throws this ValueError to prevent an ambiguous configuration where the executor could not decide which tool surface to instrument while retaining tool order and the original native executor.
Solutions
- Remove the `tools` key from options and keep the workbench (recommended, workbench is the newer surface).
- Remove or set workbench=None and keep the plain tools list.
- If you only want tools without instrumentation, check runtime.mode or use the raw model client instead of with_caveman_agent.
Example fix
// before
options = {"model_client": client, "tools": [my_tool], "workbench": wb}
agent = with_caveman_agent(options)
// after
options = {"model_client": client, "workbench": wb}
agent = with_caveman_agent(options) Defensive patterns
Strategy: validation
Validate before calling
if options.get("tools") and options.get("workbench") is not None:
raise ValueError("Pass either tools or workbench to with_caveman_agent, not both") Type guard
def has_conflicting_tool_surface(options: dict) -> bool:
return bool(options.get("tools")) and options.get("workbench") is not None Prevention
- Standardize agents on one tool surface (workbench preferred) in your codebase.
- Strip `tools` from shared base option dicts before adding `workbench` overrides.
- Add a unit test asserting each agent's options contain only one of tools/workbench.
When it happens
Trigger: Calling with_caveman_agent(options) where options contains a non-empty `tools` list AND options['workbench'] is not None. Only raised when runtime.mode is not 'off' and the model client version is supported; in off mode the check is skipped.
Common situations: Migrating from tools=[...] to a workbench-based agent but leaving the old tools list in place; merging a shared base options dict that sets tools with per-agent overrides that set workbench; passing both because an example combined them.
Related errors
- Bind runtime with…
- Expected an AutoGen Workbench or list of workbenches
- initialToolCount must be a non-negative integer
- Saved state must match the configured workbench count
- strategy must be all or deferred
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/3004bc1d063eb92a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/python/caveman_middleware/autogen.py:499
return _loaded(cls(workbench, runtime=runtime, scope=config.scope, runtime_key=config.runtime_key))
def with_caveman_model(model_client, *, runtime, scope, runtime_key="default"):
"""Wrap an existing client; recovery-free unless paired with the workbench."""
return CavemanChatCompletionClient(model_client, runtime=runtime, scope=scope, runtime_key=runtime_key)
def with_caveman_agent(options: dict, *, runtime, scope, runtime_key="default") -> dict:
"""Return native AssistantAgent constructor options, leaving its loop intact.
Accepts the native ``tools`` list or ``workbench`` (including a workbench
list), retaining tool order and the original native executor for every call.
"""
model = CavemanChatCompletionClient(options["model_client"], runtime=runtime, scope=scope, runtime_key=runtime_key)
if runtime.mode == "off" or not model.version_supported:
return {**options, "model_client": model}
if options.get("tools") and options.get("workbench") is not None:
raise ValueError("AutoGen tools and workbench are mutually exclusive")
workbench = options.get("workbench")
if workbench is None:
tools = [t if isinstance(t, BaseTool) else FunctionTool(t, description=t.__doc__ or "") for t in options.get("tools", [])]
workbench = StaticStreamWorkbench(tools)
result = {**options, "model_client": model,
"workbench": CavemanWorkbench(workbench, runtime=runtime, scope=scope, runtime_key=runtime_key)}
result.pop("tools", None)
return result
View on GitHub (pinned to 3ee70a1026)