The shared-state problem
The plugin receives a tool manager that can be reused across requests. Earlier filtering code treated that manager as request-local and removed tools from its mutable list in place. The first request could therefore change what a later request saw, even when the later request had a different group policy.
This failure depended on order. A session with a narrow allow list might run first, then a broader session would inherit the reduced container. Tests that exercised only one policy combination could pass, while a mixed sequence revealed missing tools. The useful boundary was therefore ownership: shared registration state had to remain shared and unchanged, while visibility had to belong to one request.
Copy before applying policy
The revised sequence creates a shallow container copy before applying any allow, deny, or plugin-scope rule. It also creates a new tools or func_list array, so filtering the copy cannot shorten the original manager’s list. The compatibility branch uses the same copy helper when an older manager does not expose the newer full-tool-set method.
After the copy exists, the request computes its visible names and filters only that local container. The resulting object is passed into the current runtime, then discarded with the rest of the request state. Registration remains a shared concern; policy remains local.
Keep independent capabilities independent
The maintenance pass also separated signals that had previously been easy to conflate. Tool reminders are controlled by their own prompt-injection setting. Step Image availability is derived from its image configuration. General function tools continue to use allow names, deny names, and plugin-scope rules.
These mechanisms can affect the same reply, but they answer different questions. A reminder decides what guidance appears in context, image configuration decides whether an image capability participates, and visibility policy decides which registered functions are available. Keeping those signals separate prevents one feature switch from silently changing another feature’s state.
Proving isolation
Focused tests retain the original manager, clone both supported container shapes, apply visible-name filtering to the copy, and then compare the original tool list with its starting value. The tests also confirm that the compatibility branch calls the copy helper before applying policy and that reminder handling occurs after visibility has been calculated.
This evidence is intentionally narrower than a full multi-session simulation. It verifies the ownership rule at the point where mutation used to occur: the copied container is a different object, its list can change, and the shared list remains intact.
What the boundary buys
Request-local tool state makes later behavior easier to reason about. A policy can be reviewed without reconstructing which groups ran before it, and compatibility code follows the same ownership rule as the current interface. Maintenance tests can target the copy and filter boundary instead of recreating an entire chat environment.
The same structure also leaves room for later capabilities. New tool classes can define their own configuration and visibility rules while the shared registry stays immutable during request handling. That keeps additions predictable without exposing deployment identities, runtime addresses, or private configuration.