JuliusBrussee/caveman · error · ValueError
Saved state must match the configured workbench count
Error message
Saved state must match the configured workbench count
What it means
When a CavemanWorkbench was constructed with a list of delegate workbenches, load_state expects the saved state to contain a 'workbenches' array whose length equals the number of delegates, restoring each delegate's state positionally. A mismatch raises ValueError instead of silently dropping or misaligning saved states.
Solutions
- Re-save state with save_state() from the same CavemanWorkbench configuration (same delegate count) before calling load_state.
- If the delegate count intentionally changed, migrate the saved state: reshape it into a 'workbenches' list matching the new count.
- If you actually have a single workbench's state, load it into a single-workbench CavemanWorkbench instead.
- Wrap load_state in try/except ValueError to detect stale state and fall back to a fresh save_state.
Example fix
// before await wrapper.load_state(saved_single_state) # saved from 1 workbench, wrapper has 2 // after assert len(saved["workbenches"]) == len(wrapper.delegates) await wrapper.load_state(saved)
Defensive patterns
Strategy: try-catch
Validate before calling
if isinstance(wrapper.workbench, list) and len(state.get("workbenches", [])) != len(wrapper.delegates):
raise ValueError("state/delegate count mismatch; re-save state") Try / catch
try:
await wrapper.load_state(state)
except ValueError:
# stale or mis-shaped state; start fresh
fresh = await wrapper.save_state()
await wrapper.load_state(fresh) Prevention
- Always pair save_state/load_state from the same CavemanWorkbench instance/configuration.
- Version-tag persisted state with the delegate count so migrations are explicit.
- Wrap load_state in try/except ValueError with a fallback to fresh state.
- Add a count assertion before loading when restoring from checkpoints.
When it happens
Trigger: Calling load_state with a scalar state (saved from a single-workbench configuration) on a multi-workbench CavemanWorkbench, or with a 'workbenches' list of a different length (delegates added/removed between save and load).
Common situations: Persisting AutoGenAgent state, changing the number of delegate workbenches between runs, sharing one saved state file across differently configured agents, or saving from a single workbench and loading into a list-configured wrapper.
Related errors
- Expected an AutoGen Workbench or list of workbenches
- assembly slot changed after being declared stable
- AutoGen requires a stable Caveman Scope for each agent or…
- AutoGen tools and workbench are mutually exclusive
- Bind runtime with…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/d66716a58e377e19.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/python/caveman_middleware/autogen.py:467
except BaseException as error:
if first_error is None:
first_error = error
if first_error is not None:
raise first_error
async def reset(self):
for workbench in self.delegates:
await workbench.reset()
async def save_state(self):
if type(self.workbench) is list:
return {"workbenches": [await workbench.save_state() for workbench in self.delegates]}
return await self.workbench.save_state()
async def load_state(self, state):
if type(self.workbench) is list:
if len(state.get("workbenches", [])) != len(self.delegates):
raise ValueError("Saved state must match the configured workbench count")
for workbench, saved in zip(self.delegates, state["workbenches"]):
await workbench.load_state(saved)
else:
await self.workbench.load_state(state)
def _to_config(self):
config = [workbench.dump_component() for workbench in self.delegates] if type(self.workbench) is list else self.workbench.dump_component()
return CavemanWorkbenchConfig(workbench=config, scope=self.scope, runtime_key=self.runtime_key)
@classmethod
def _from_config(cls, config):
runtime = _runtime(config.runtime_key)
workbench = [Workbench.load_component(item) for item in config.workbench] if type(config.workbench) is list else Workbench.load_component(config.workbench)
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."""View on GitHub (pinned to 3ee70a1026)