JuliusBrussee/caveman · error · ValueError

assembly slot id must be non-empty and unique

Error message

assembly slot id must be non-empty and unique: {slot.id!r}

What it means

Every assembly slot must have a non-empty `id`, and slot ids within a single `assemble()` call must be unique. The library deduplicates content per slot id, so duplicates or blanks are rejected with this ValueError.

Solutions

  1. Give each slot a unique, non-empty `id` before calling `assemble()`.
  2. De-duplicate or rename slots when merging lists from multiple sources.
  3. Use deterministic, descriptive ids per logical role (e.g. "system", "tools", "history") so each appears once.

Example fix

// before
slots = [AssemblySlot("system", "stable", a), AssemblySlot("system", "stable", b)]
// after
slots = [AssemblySlot("system", "stable", a), AssemblySlot("tools", "session", b)]
Defensive patterns

Strategy: validation

Validate before calling

ids = [s.id for s in opts.slots]
assert all(ids), "empty slot id"
assert len(ids) == len(set(ids)), f"duplicate slot ids: {ids}"

Try / catch

try:
    result = cave.assemble(opts)
except ValueError as e:
    logger.error("slot id problem: %s", e)
    raise

Prevention

When it happens

Trigger: Calling `assemble()` with a slot whose `id` is `""` or `None`, or listing two slots with the same id (e.g. two "system-prompt" slots in one options).

Common situations: Generating slot ids in a loop that defaults to a constant, or merging slot lists from two sources without de-duplicating.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at packages/sdk/python/caveman_cloud/core.py:446

        Pure client-side: no account and no network call. The hash ledger is
        in-process and scoped by ``session_id``; deterministic slot content
        across processes remains the builder's obligation.

        ``emit_cache_hints="gateway"`` (default) emits no provider hint so the
        gateway optimizer retains attribution. ``"self"`` places provider
        hints for direct calls but mints nothing.
        """
        provider = options.provider.strip().lower()
        if not options.model or not options.session_id:
            raise ValueError("assemble requires model and session_id")
        if options.emit_cache_hints not in {"gateway", "self", "none"}:
            raise ValueError(f"unknown emit_cache_hints value {options.emit_cache_hints!r}")

        ids: set[str] = set()
        normalized: list[AssemblySlot] = []
        for slot in options.slots:
            if not slot.id or slot.id in ids:
                raise ValueError(f"assembly slot id must be non-empty and unique: {slot.id!r}")
            ids.add(slot.id)
            if slot.stability not in {"stable", "session", "volatile"}:
                raise ValueError(
                    f"assembly slot {slot.id!r} has unknown stability {slot.stability!r}"
                )
            try:
                content_json = _canonical_json(slot.content)
                content = json.loads(content_json)
            except (TypeError, ValueError):
                raise ValueError(
                    f"assembly slot {slot.id!r} content is not JSON-serializable"
                ) from None
            if slot.stability != "volatile":
                key = (options.session_id, slot.id)
                content_hash = _sha256_hex(content_json)
                with self._assembly_hash_lock:
                    previous = self._assembly_hash_ledger.get(key)
                    if previous is not None and previous != content_hash:

View on GitHub (pinned to 3ee70a1026)