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

  1. Re-save state with save_state() from the same CavemanWorkbench configuration (same delegate count) before calling load_state.
  2. If the delegate count intentionally changed, migrate the saved state: reshape it into a 'workbenches' list matching the new count.
  3. If you actually have a single workbench's state, load it into a single-workbench CavemanWorkbench instead.
  4. 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

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


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)