Skip to content

symfonic.capabilities.tools.stages.policy

policy

Policy stage โ€” the registry's own filters, as a selection stage.

ToolRegistry.filter and tools_within_budget answer the same questions this stage answers, from the same metadata, and a parity suite pins them together. The difference is position: the registry answers them once at construction, this stage answers them per turn, in a declared order, with the result recorded in the trace.

PolicyStage

PolicyStage(*, catalog: ToolCatalog, max_safety_level: Any = None, budget_usd: float | None = None, include_unknown_cost: bool = True, available_capabilities: set[type] | None = None, agent_visible_only: bool = False, categories: Sequence[Any] | None = None)

Drop tools the configured policy forbids.

A tool absent from the catalogue is kept. Its metadata is unknown, and denying it would make "the registry has never heard of this tool" indistinguishable from "policy forbids this tool" โ€” the first is a wiring bug that has to stay visible.

Source code in src/symfonic/capabilities/tools/stages/policy.py
def __init__(
    self,
    *,
    catalog: ToolCatalog,
    max_safety_level: Any = None,
    budget_usd: float | None = None,
    include_unknown_cost: bool = True,
    available_capabilities: set[type] | None = None,
    agent_visible_only: bool = False,
    categories: Sequence[Any] | None = None,
) -> None:
    self._catalog = catalog
    self._max_safety_rank = _safety_rank(max_safety_level)
    self._budget_usd = budget_usd
    self._include_unknown_cost = include_unknown_cost
    self._available_capabilities = available_capabilities
    self._agent_visible_only = agent_visible_only
    self._categories = (
        None if categories is None else {_category_value(c) for c in categories}
    )