Source code for simvx.core.ai.blackboard

"""Blackboard: the shared key-value store deciders read and write.

Perception writes percepts into a `Blackboard`; deciders (behaviour trees,
state machines, utility scorers, or an LLM brain) read them, and high-level
deciders write *intent* (current goal, mood, target) that low-level deciders
execute. An optional parent chain lets a squad share intent: a child board
falls back to its parent for keys it does not define locally, so a squad-lead
brain can publish one shared goal that every member reads while keeping
per-member scratch state private.
"""

from __future__ import annotations

from collections.abc import Iterator, Mapping
from typing import Any

_MISSING = object()


[docs] class Blackboard: """A dict-like store with optional parent fallback.""" __slots__ = ("_data", "_parent") def __init__(self, initial: Mapping[str, Any] | None = None, *, parent: Blackboard | None = None) -> None: self._data: dict[str, Any] = dict(initial) if initial else {} self._parent = parent
[docs] def get(self, key: str, default: Any = None) -> Any: value = self._data.get(key, _MISSING) if value is not _MISSING: return value if self._parent is not None: return self._parent.get(key, default) return default
[docs] def set(self, key: str, value: Any) -> None: self._data[key] = value
[docs] def has(self, key: str) -> bool: return key in self._data or (self._parent is not None and self._parent.has(key))
[docs] def delete(self, key: str) -> None: self._data.pop(key, None)
[docs] def update(self, mapping: Mapping[str, Any]) -> None: self._data.update(mapping)
[docs] def clear(self) -> None: """Clear only this board's local entries (parent untouched).""" self._data.clear()
[docs] def local_keys(self) -> list[str]: return list(self._data)
[docs] def as_dict(self, *, inherited: bool = True) -> dict[str, Any]: """Flatten to a plain dict; local keys shadow inherited ones.""" if inherited and self._parent is not None: merged = self._parent.as_dict(inherited=True) merged.update(self._data) return merged return dict(self._data)
[docs] def child(self) -> Blackboard: """A new board that falls back to this one for missing keys.""" return Blackboard(parent=self)
[docs] def __getitem__(self, key: str) -> Any: value = self.get(key, _MISSING) if value is _MISSING: raise KeyError(key) return value
[docs] def __setitem__(self, key: str, value: Any) -> None: self._data[key] = value
[docs] def __delitem__(self, key: str) -> None: del self._data[key]
[docs] def __contains__(self, key: str) -> bool: return self.has(key)
[docs] def __iter__(self) -> Iterator[str]: return iter(self.as_dict())
[docs] def __len__(self) -> int: return len(self.as_dict())
[docs] def __repr__(self) -> str: tag = " +parent" if self._parent is not None else "" return f"Blackboard({self._data!r}{tag})"