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})"