{"record":{"id":"26a064ef9077f85c","repo":"deepset-ai/haystack","slug":"state-schema-keys-reserved-used-are-reserved-for","errorCode":null,"errorMessage":"state_schema keys {reserved_used} are reserved for Agent internal state and cannot be redefined. Reserved keys: {sorted(reserved_keys)}.","messagePattern":"state_schema keys (.+?) are reserved for Agent internal state and cannot be redefined\\. Reserved keys: (.+?)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"haystack/components/agents/agent.py","lineNumber":476,"sourceCode":"        \"\"\"\n        # --- Validation ---\n        self._chat_generator_supports_tools: bool = \"tools\" in inspect.signature(chat_generator.run).parameters\n        # We use an explicit None check for tools b/c testing for truthiness calls __len__, which for SearchableToolset\n        # would iterate and prematurely warm it up at init.\n        if tools is not None and not self._chat_generator_supports_tools:\n            raise TypeError(\n                f\"{type(chat_generator).__name__} does not accept tools parameter in its run method. \"\n                \"The Agent component requires a chat generator that supports tools when tools are provided.\"\n            )\n\n        if exit_conditions is None:\n            exit_conditions = [\"text\"]\n\n        if state_schema is not None:\n            reserved_keys = _RUN_METADATA_STATE_KEYS.keys() | _INTERNAL_STATE_KEYS.keys()\n            reserved_used = sorted(set(state_schema) & reserved_keys)\n            if reserved_used:\n                raise ValueError(\n                    f\"state_schema keys {reserved_used} are reserved for Agent internal state and \"\n                    f\"cannot be redefined. Reserved keys: {sorted(reserved_keys)}.\"\n                )\n            _validate_schema(state_schema)\n        _validate_prompt_message_blocks(user_prompt, system_prompt)\n        if tool_concurrency_limit < 1:\n            raise ValueError(\"tool_concurrency_limit must be greater than or equal to 1.\")\n\n        hooks = hooks or {}\n        _validate_hooks(hooks)\n\n        # --- Attributes ---\n        self.chat_generator = chat_generator\n        # We use an explicit None check for tools b/c testing for truthiness calls __len__, which for SearchableToolset\n        # would iterate and prematurely warm it up at init.\n        self.tools = tools if tools is not None else []\n        self.system_prompt = system_prompt\n        self.user_prompt = user_prompt","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/components/agents/agent.py#L458-L494","documentation":"_skill_dir looks up the requested skill name in the warmed catalog and, on KeyError, raises a new KeyError listing the available skill names (or 'none'). This replaces the bare KeyError so callers can see which skills exist. It surfaces from load_skill, _list_skill_files, and read_skill_file.","triggerScenarios":"Calling store.load_skill('typo-name'), read_skill_file('typo-name', ...), or an agent requesting a skill name that isn't in skills_dir; also when warm_up failed earlier and the catalog is empty (though that usually raises error 402/403/404 first).","commonSituations":"Typos or case mismatch ('My-Skill' vs 'my-skill'); stale skill name after a rename; LLM agent hallucinating a skill name; skills_dir content changed but the store instance cached an old catalog.","solutions":["Use one of the names listed in the error's 'Available skills' list.","Call store.list_skills() to get current names and fix the caller's lookup.","If the skill should exist, check the frontmatter 'name' (or directory name) in skills_dir and that the store was constructed against the right directory."],"exampleFix":"// before\nskill = store.load_skill(\"code-review\")\n// after\navailable = store.list_skills()\nskill = store.load_skill(available[0].name if available else \"code-review\")","handlingStrategy":"try-catch","validationCode":"def resolve_skill(store, wanted: str):\n    available = {s.name for s in store.list_skills()}\n    if wanted in available:\n        return wanted\n    lowered = {n.lower(): n for n in available}\n    return lowered.get(wanted.lower())","typeGuard":null,"tryCatchPattern":"try:\n    skill = store.load_skill(name)\nexcept KeyError as e:\n    logger.warning(\"Skill %r not found; available: %s\", name, e)\n    skill = None  # or retry with a corrected name","preventionTips":["Call list_skills() and pick names from it instead of hardcoding strings","Match skill names case-sensitively and exactly as declared in frontmatter","Refresh/reconstruct the store after changing skills_dir contents","When an LLM picks skill names, constrain it to the list_skills() output"],"tags":["skill-store","keyerror","lookup","unknown-name"],"backgroundTag":"unknown-identifier","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}